Setting up the Three.js renderer
Introduction
The previous articles in this series were about:
- Part 1: Reading VTK format particles with Javascript in a browser
- Part 2: Saving the read-in particle data in a Vuex store
- Part 3: Initialization of a store and the user interface before plotting the particles with Three.js.
In this article we will discuss the actual process of visualization of the particles.
The particles we are interested in are oriented ellipsoids.
The graphics panel
In the main-panel
Vue component introduced in Part 3, we had declared a graphics panel
component called three-graphics-panel
. A simple Vue template for this component can be
written as:
<template>
<div id='three-graphics-container'>
<div class="uk-card uk-card-default uk-card-large">
<div class="uk-card-body">
<!-- The renderer Vue component -->
<three-renderer v-bind:size="{w:500, h:500}">
<!-- The scene Vue component -->
<three-scene v-bind:size="size">
<!-- The camera Vue component -->
<three-camera v-bind:size="size"
v-bind:position="{x: 100, z: 15 }">
</three-camera>
<!-- The particles Vue component -->
<three-ellipsoid-particles> </three-ellipsoid-particles>
</three-scene>
</three-renderer>
</div>
</div>
</div>
</template>
<script src="./ThreeGraphicsPanel.ts"> </script>
As before, the CSS classes used in this example are from Uikit3. The graphics panel uses
four child Vue components, three-renderer
, three-scene
, three-camera
, and
three-ellipsoid-particles
. The implementation of ThreeGraphicsPanel.ts
is also
straightforward in this case:
import * as Vue from "vue";
import {Component} from "av-ts";
@Component({
name: 'three-graphics-panel'
})
export default class ThreeGraphicsPanel extends Vue {
name: string = "ThreeGraphicsPanel";
size: Object = {
w: 200,
h: 200
};
}
The size
property is passed on to the children of three-graphics-panel and determines the
default size of the window in which the particles will be displayed. This property can be
accessed by the children by using the
“passing data with props approach.”
The renderer template
The first component instantiated in three-graphics-panel
is the three-renderer
component which
is passed the size
argument using the v-bind
directive. Let use first examine
the ThreeRenderer.vue
code:
<template>
<div id="three-renderer-div">
<slot></slot>
<div ref="three-graphics-container"
id="three-renderer-graphics-div">
</div>
</div>
</template>
<script src="./ThreeRenderer.ts"> </script>
The first new aspect to notice is the use of the
slot
tag. This
is needed to make sure that the three-scene
, three-camera
, and three-ellipsoid-particles
from the three-graphics-panel
parent components are correctly instantiated and interpolated
into the DOM.
The second new aspect is the use of the ref
attribute
in the div
element after the slot
. This element is used to create the HTML canvas as
a child of the renderer component.
We could add event handlers to this template to handle interactions with the camera. That aspect of the user interface will be discussed in a future article.
The renderer implementation
The renderer component is implemented in ThreeRenderer.ts
which contains the following
code:
import * as Vue from "vue";
import THREE = require('three');
import { Component, Lifecycle, Prop, p } from 'av-ts';
import Store from '../vuex/Store';
interface ScreenSize {
left : number, top : number, width : number, height : number
}
@Component({
name: 'ThreeRenderer'
})
export default class ThreeRenderer extends Vue {
// These are the "prop" variables used by Vue
@Prop
size = p({
type: Object, // { w, h }
required: true
});
@Prop
renderer = p({
type: THREE.WebGLRenderer
});
// These are the "data" variables used by Vue
public d_renderer: THREE.WebGLRenderer;
private d_size: ScreenSize;
// These are the computed properties
get getScene() {
return Store.getters.scene;
}
get getCamera() {
return Store.getters.camera;
}
// These are the lifecycle hooks
@Lifecycle
created() {
this.d_renderer = this.renderer;
if (!(this.d_renderer instanceof THREE.WebGLRenderer)) {
this.d_renderer = new THREE.WebGLRenderer({ antialias: true });
}
let w = (<any>this.size).w;
let h = (<any>this.size).h;
this.d_size = {left: 0, top: 0, width: w, height: w};
this.d_renderer.setSize(w, h);
this.d_renderer.setClearColor(0x52576e)
}
@Lifecycle
mounted() {
this.d_renderer.domElement.setAttribute("id", "three-graphics-canvas");
if ((this.$refs)["three-graphics-container"]) {
let el = (this.$refs)["three-graphics-container"];
(<any>el).appendChild(this.d_renderer.domElement);
// Update based on actual screen size
this.setActualScreenSize(this.d_renderer.domElement);
this.d_renderer.setSize(this.d_size.width, this.d_size.height);
let camera = Store.getters.camera;
camera.aspect = this.d_size.width / this.d_size.height;
camera.updateProjectionMatrix();
Store.commit('SET_CAMERA', camera);
}
this.animate();
}
@Lifecycle
beforeDestroy() {
Store.commit('DELETE_SCENE');
Store.commit('DELETE_CAMERA');
}
// If there is a resize, compute the new screen size
private setActualScreenSize(domElement: HTMLCanvasElement) : void {
let box = domElement.getBoundingClientRect();
let d = domElement.ownerDocument.docsumentElement;
this.d_size.left = box.left + window.docsXOffset - d.clientLeft;
this.d_size.top = box.top + window.docsYOffset - d.clientTop;
this.d_size.width = box.width;
this.d_size.height = box.height;
}
// Start the animation loop
private animate() {
requestAnimationFrame( this.animate );
this.render();
}
// Do the actual rendering using the THREE.js render method
private render() {
this.d_renderer.render(Store.getters.scene, Store.getters.camera);
}
}
The main thing to note here is that we are using the THREE.WebGLRenderer, and
the creation of the canvas
element as a child of the renderer div element.
The canvas element is created in the mount
stage of the instance lifecycle and
all the plotting is done on that element.
Remarks
In the next part of this article we’ll discuss more details of setting up the scene and camera.