Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to the "vscode-3dpreview" extension will be documented in th

Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.

## Unreleased

- Add the `3dpreview.upAxis` setting and GUI dropdown to choose which axis of the file points up (+X, -X, +Y, -Y, +Z, -Z). The model is rotated in place, the axes helper follows the file's coordinate system and the grid stays on the world floor ([#14](https://github.com/tatsy/vscode-3d-preview/issues/14)).

## v0.2.5

- Restore free rotation with `TrackballControls` and add the `3dpreview.cameraControls` setting to switch between trackball and orbit controls ([#12](https://github.com/tatsy/vscode-3d-preview/issues/12)).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ All settings live under the `3dpreview` namespace and act as the initial values
| Setting | Default | Description |
|:--|:--|:--|
| `hideControlsOnStart` | `false` | Fold the control panel when the viewer opens. |
| `upAxis` | `"+Y"` | Axis of the file's coordinate system that points up in the viewer. Choose `"+Z"` for Z-up data such as Blender exports and `"-Y"` for OpenCV/COLMAP conventions. The axes helper shows the file's own axes; the grid stays on the world floor. |
| `cameraControls` | `"trackball"` | `"trackball"` allows free rotation in any direction. `"orbit"` keeps the camera upright and cannot pass over the poles. |
| `showMesh` | `true` | Show mesh triangles. |
| `flatShading` | `false` | Use flat shading instead of smooth shading for meshes. STL files are always rendered flat because the format stores no shared vertices. |
Expand Down
42 changes: 21 additions & 21 deletions media/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,38 @@ function createModelLoader(fileToLoad) {
}
}

function getBBoxCenter(geometry) {
geometry.computeBoundingBox();

var center = new THREE.Vector3();
center.x = (geometry.boundingBox.max.x + geometry.boundingBox.min.x) / 2;
center.y = (geometry.boundingBox.max.y + geometry.boundingBox.min.y) / 2;
center.z = (geometry.boundingBox.max.z + geometry.boundingBox.min.z) / 2;
return center;
// Accept either a THREE.Box3 or a geometry whose bounding box is (re)computed.
function toBox3(geometryOrBox) {
if (geometryOrBox.isBox3) {
return geometryOrBox;
}
geometryOrBox.computeBoundingBox();
return geometryOrBox.boundingBox;
}

function getBBoxMaxExtent(geometry) {
geometry.computeBoundingBox();

var cx = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
var cy = geometry.boundingBox.max.y - geometry.boundingBox.min.y;
var cz = geometry.boundingBox.max.z - geometry.boundingBox.min.z;
function getBBoxCenter(geometryOrBox) {
const box = toBox3(geometryOrBox);
return box.getCenter(new THREE.Vector3());
}

return Math.max(cx, Math.max(cy, cz));
function getBBoxMaxExtent(geometryOrBox) {
const box = toBox3(geometryOrBox);
const size = box.getSize(new THREE.Vector3());
return Math.max(size.x, Math.max(size.y, size.z));
}

function autoCameraPos(geometry) {
geometry.computeBoundingBox();
function autoCameraPos(geometryOrBox) {
const box = toBox3(geometryOrBox);

var cx = (geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2;
var cy = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) / 2;
var cz = (geometry.boundingBox.max.z - geometry.boundingBox.min.z) / 2;
var cx = (box.max.x - box.min.x) / 2;
var cy = (box.max.y - box.min.y) / 2;
var cz = (box.max.z - box.min.z) / 2;
var sx = cx > 0 ? 1.0 : -1.0;
var sy = cy > 0 ? 1.0 : -1.0;
var sz = cz > 0 ? 1.0 : -1.0;
var d = Math.max(cx, Math.max(cy, cz)) * 2.0;

var center = getBBoxCenter(geometry);
var center = getBBoxCenter(box);
var cameraPos = new THREE.Vector3(d * sx, d * sy, d * sz);
cameraPos.add(center);
cameraPos.multiplyScalar(1);
Expand Down
113 changes: 87 additions & 26 deletions media/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ import * as utils from './utils.js';
// The point size slider is mapped onto [0, MAX_POINT_PIXEL_SIZE] in that mode.
const MAX_POINT_PIXEL_SIZE = 30;

// Rotation that brings the file's up axis to three.js' +Y. Each entry is a pure
// rotation, so handedness is preserved and no mirroring is introduced.
const UP_AXIS_ROTATIONS = {
'+X': new THREE.Euler(0, 0, Math.PI / 2),
'-X': new THREE.Euler(0, 0, -Math.PI / 2),
'+Y': new THREE.Euler(0, 0, 0),
'-Y': new THREE.Euler(Math.PI, 0, 0),
'+Z': new THREE.Euler(-Math.PI / 2, 0, 0),
'-Z': new THREE.Euler(Math.PI / 2, 0, 0),
};

class Viewer {
controls;
points;
Expand Down Expand Up @@ -58,6 +69,13 @@ class Viewer {
const light = new THREE.HemisphereLight(0x888888, 0x333333, 1.0);
this.scene.add(light);

// All loaded objects (points, mesh, wireframe) live in this group, so that the
// file's coordinate system can be re-oriented by rotating the group alone.
// Camera and grid helper stay in world space.
this.model = new THREE.Group();
this.model.name = 'model';
this.scene.add(this.model);

// Camera
this.camera = new THREE.PerspectiveCamera(
45.0,
Expand Down Expand Up @@ -87,22 +105,46 @@ class Viewer {
this.stats.end();
}

// Bounding box of the loaded data in the file's own coordinate system.
localBBox() {
this.points.geometry.computeBoundingBox();
return this.points.geometry.boundingBox;
}

// Bounding box of the loaded data in world space, i.e. after the up-axis rotation.
worldBBox() {
this.model.updateMatrixWorld(true);
return new THREE.Box3().setFromObject(this.points);
}

// Rotate the model group so that the file's up axis points to +Y. The rotation is
// taken about the model's bounding-box centre, so the model stays where it is and
// only its orientation changes. The camera is intentionally left untouched.
applyUpAxis() {
const rotation = UP_AXIS_ROTATIONS[this.params.upAxis] || UP_AXIS_ROTATIONS['+Y'];
const center = utils.getBBoxCenter(this.localBBox());
this.model.rotation.copy(rotation);
this.model.position.copy(center).sub(center.clone().applyEuler(rotation));
this.model.updateMatrixWorld(true);
}

updateHelpers() {
// Remove current helpers
if (this.gridHelper !== null) {
this.scene.remove(this.gridHelper);
}

if (this.axesHelper !== null) {
this.scene.remove(this.axesHelper);
this.model.remove(this.axesHelper);
}

// BBox center
const center = utils.getBBoxCenter(this.points.geometry);
const extent = utils.getBBoxMaxExtent(this.points.geometry);

// Grid helper
// Grid helper: lives in world space so that it always reads as the floor,
// whatever up axis the file uses.
if (this.params.showGridHelper) {
const worldBox = this.worldBBox();
const center = utils.getBBoxCenter(worldBox);
const extent = utils.getBBoxMaxExtent(worldBox);

const size = this.params.gridHelper.size;
const unit = this.params.gridHelper.unit;
const divisions = size / unit;
Expand All @@ -114,28 +156,37 @@ class Viewer {
const colorCenterLine = new THREE.Color('#888888');
const colorGrid = new THREE.Color('#888888');
this.gridHelper = new THREE.GridHelper(size, divisions, colorCenterLine, colorGrid);
this.gridHelper.position.x += center.x - extent * 0.5;
this.gridHelper.position.y += center.y - extent * 0.5;
this.gridHelper.position.z += center.z - extent * 0.5;
this.gridHelper.material.linewidth = 10;
this.gridHelper.name = 'gridHelper';
}
this.gridHelper.position.set(
center.x - extent * 0.5,
center.y - extent * 0.5,
center.z - extent * 0.5
);

this.scene.add(this.gridHelper);
}

// Axis helper
// Axes helper: child of the model group, so it shows the axes of the file's
// own coordinate system and follows the up-axis rotation.
if (this.params.showAxesHelper) {
const localBox = this.localBBox();
const center = utils.getBBoxCenter(localBox);
const extent = utils.getBBoxMaxExtent(localBox);

if (this.axesHelper === null) {
this.axesHelper = new THREE.AxesHelper(extent);
this.axesHelper.position.x += center.x - extent * 0.5;
this.axesHelper.position.y += center.y - extent * 0.5;
this.axesHelper.position.z += center.z - extent * 0.5;
this.axesHelper.material.linewidth = 10;
this.axesHelper.name = 'axesHelper';
}
this.axesHelper.position.set(
center.x - extent * 0.5,
center.y - extent * 0.5,
center.z - extent * 0.5
);

this.scene.add(this.axesHelper);
this.model.add(this.axesHelper);
}
}

Expand Down Expand Up @@ -177,9 +228,9 @@ class Viewer {
this.mesh.material.needsUpdate = true;
}

this.scene.remove(this.mesh);
this.model.remove(this.mesh);
if (this.params.showMesh) {
this.scene.add(this.mesh);
this.model.add(this.mesh);
}
}

Expand All @@ -188,9 +239,9 @@ class Viewer {
this.wireframe.material.color = new THREE.Color(this.params.wireframeColor);
this.wireframe.material.linewidth = this.params.wireframeWidth;

this.scene.remove(this.wireframe);
this.model.remove(this.wireframe);
if (this.params.showWireframe) {
this.scene.add(this.wireframe);
this.model.add(this.wireframe);
}
}
}
Expand All @@ -208,7 +259,7 @@ class Viewer {
const target =
this.controls !== undefined
? this.controls.target.clone()
: utils.getBBoxCenter(this.points.geometry);
: utils.getBBoxCenter(this.worldBBox());

if (this.controls !== undefined) {
this.controls.dispose();
Expand Down Expand Up @@ -298,7 +349,7 @@ class Viewer {
console.warn(e);
self.monochrome = true;
}
self.scene.add(self.points);
self.model.add(self.points);

self.onMeshLoaded();
self.updateHelpers();
Expand All @@ -321,7 +372,7 @@ class Viewer {
self.mesh.castShadow = true;
self.mesh.receiveShadow = true;
self.mesh.name = base + '_mesh';
self.scene.add(self.mesh);
self.model.add(self.mesh);

// Wireframe
const wireMaterial = new LineMaterial({
Expand All @@ -332,26 +383,29 @@ class Viewer {
const edges = new WireframeGeometry2(geometry);
self.wireframe = new Line2(edges, wireMaterial);
self.wireframe.name = base + '_wireframe';
self.scene.add(self.wireframe);
self.model.add(self.wireframe);
} catch (e) {
console.error(e);
}
});
}

onMeshLoaded() {
// Orient the model according to the configured up axis before placing the camera
this.applyUpAxis();
const worldBox = this.worldBBox();

// Camera setup
this.points.geometry.computeBoundingBox();
const camTarget = utils.getBBoxCenter(this.points.geometry);
const camPos = utils.autoCameraPos(this.points.geometry);
const camTarget = utils.getBBoxCenter(worldBox);
const camPos = utils.autoCameraPos(worldBox);

this.camera.position.copy(camPos);
this.camera.lookAt(camTarget);
this.setupControls();
window.addEventListener('resize', () => this.onWindowResize());

// GUI setup
const extent = utils.getBBoxMaxExtent(this.points.geometry);
const extent = utils.getBBoxMaxExtent(worldBox);
this.params.pointSize = extent / 100.0;
this.params.pointMaxSize = extent / 10.0;

Expand Down Expand Up @@ -419,6 +473,13 @@ class Viewer {
.add(this.params, 'cameraControls', { Trackball: 'trackball', Orbit: 'orbit' })
.name('Camera controls')
.onChange(() => this.setupControls());
this.gui
.add(this.params, 'upAxis', Object.keys(UP_AXIS_ROTATIONS))
.name('Up axis')
.onChange(() => {
this.applyUpAxis();
this.updateHelpers();
});

let folder = this.gui.addFolder('Grid Helper');
folder.open();
Expand Down
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,19 @@
"minimum": 0,
"description": "Intensity of the directional light that follows the camera. Set 0 to disable it."
},
"3dpreview.upAxis": {
"type": "string",
"default": "+Y",
"enum": [
"+X",
"-X",
"+Y",
"-Y",
"+Z",
"-Z"
],
"description": "Axis of the file's coordinate system that points up in the viewer. Use \"+Z\" for Z-up data such as Blender exports and \"-Y\" for OpenCV/COLMAP conventions."
},
"3dpreview.cameraControls": {
"type": "string",
"default": "trackball",
Expand Down
1 change: 1 addition & 0 deletions src/meshProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class MeshViewProvider implements vscode.CustomReadonlyEditorProvider<Mes
fogDensity: config.get('fogDensity', 0.01),
lightIntensity: config.get('lightIntensity', 1.0),
cameraControls: config.get('cameraControls', 'trackball'),
upAxis: config.get('upAxis', '+Y'),
};
return `<meta id="vscode-3dviewer-data" data-settings="${JSON.stringify(initialData).replace(
/"/g,
Expand Down