Skip to content

Instantly share code, notes, and snippets.

@nickyvanurk
Last active May 30, 2023 11:09
Show Gist options
  • Save nickyvanurk/18c13872ee61824ec92a511abbd6eede to your computer and use it in GitHub Desktop.
Save nickyvanurk/18c13872ee61824ec92a511abbd6eede to your computer and use it in GitHub Desktop.
Threejs-react-starter-template (Vanilla)
import {
BoxGeometry,
Color,
HemisphereLight,
Mesh,
MeshBasicMaterial,
MeshNormalMaterial,
PerspectiveCamera,
PlaneGeometry,
Scene,
WebGLRenderer,
} from 'three';
import { MapControls } from 'three/examples/jsm/controls/OrbitControls';
import Stats from 'three/examples/jsm/libs/stats.module';
import { GUI } from 'lil-gui';
/**
* Three.js map viewer
*/
export class ThreeMapViewer {
container: HTMLElement;
camera!: PerspectiveCamera;
renderer!: WebGLRenderer;
scene!: Scene;
controls!: MapControls;
time = { last: performance.now(), now: 0, dt: 0 };
stats = Stats();
panel = new GUI({ autoPlace: false });
settings = { performance: true };
mesh!: Mesh;
drawCallsPanel!: Stats.Panel;
geometriesPanel!: Stats.Panel;
frameRequestId!: number;
/**
* Initial configuration
*/
constructor(containerClass: string) {
this.container = document.querySelector(containerClass) as HTMLElement;
this.createPanel();
this.createScene();
this.createCamera();
this.createLights();
this.createControls();
this.createMeshes();
this.createRenderer();
this.render();
}
destroy() {
window.cancelAnimationFrame(this.frameRequestId);
}
/**
* All debug settings get initialized here
*/
createPanel() {
this.stats.domElement.style.position = 'absolute';
this.stats.domElement.style.display = 'flex';
this.stats.domElement.style.flexDirection = 'column';
this.container.appendChild(this.stats.domElement);
this.drawCallsPanel = Stats.Panel('Draw Calls', '#0ff', '#002');
this.stats.addPanel(this.drawCallsPanel);
this.geometriesPanel = Stats.Panel('Geometries', '#0ff', '#002');
this.stats.addPanel(this.geometriesPanel);
this.panel.domElement.style.position = 'absolute';
this.panel.domElement.style.right = '0';
this.panel.domElement.style.zIndex = '10';
this.container.appendChild(this.panel.domElement);
const visibility = this.panel.addFolder('Visibility');
visibility
.add(this.settings, 'performance')
.onChange((show: boolean) => (this.stats.dom.style.display = show ? 'flex' : 'none'));
}
/**
* The main scene gets initialized here
*/
createScene() {
this.scene = new Scene();
this.scene.background = new Color(0x050505);
}
/**
* All cameras get initialized here
*/
createCamera() {
this.camera = new PerspectiveCamera(71, this.container.clientWidth / this.container.clientHeight, 0.1, 4000);
this.camera.position.set(0, 5, 10);
this.camera.lookAt(0, 1, 0);
}
/**
* All lights get initialized here
*/
createLights() {
const light = new HemisphereLight();
this.scene.add(light);
}
/**
* All meshes and 3D models, along with shaders get initialized here
*/
createMeshes() {
const plane = new Mesh(new PlaneGeometry(100, 100), new MeshBasicMaterial({ color: 0xffffff }));
plane.rotateX(-Math.PI / 2);
this.scene.add(plane);
const box = new Mesh(new BoxGeometry(1, 1, 1), new MeshNormalMaterial());
box.position.y = 1;
this.scene.add(box);
this.mesh = box;
}
/**
* All controls gets initialized here
*/
createControls() {
this.controls = new MapControls(this.camera, this.container);
this.controls.enableDamping = true;
this.controls.panSpeed = 1.2;
this.controls.dampingFactor *= 2;
}
/**
* Renderer and its settings gets initialized here
*/
createRenderer() {
this.renderer = new WebGLRenderer({ antialias: true });
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.domElement.style.position = 'absolute';
this.renderer.domElement.style.top = '0';
this.container.appendChild(this.renderer.domElement);
}
/**
* Render scene and camera
*/
render() {
this.frameRequestId = window.requestAnimationFrame(this.render.bind(this));
this.time.now = performance.now();
this.time.dt = (this.time.now - this.time.last) / 1000;
this.time.last = this.time.now;
this.mesh.rotation.x += 1 * this.time.dt;
this.mesh.rotation.y += 1 * this.time.dt;
this.controls.update();
this.renderer.render(this.scene, this.camera);
this.stats.update();
this.geometriesPanel.update(this.renderer.info.memory.geometries, 2);
this.drawCallsPanel.update(this.renderer.info.render.calls, 2);
}
/**
* Resize renderer and camera frustum
*/
onWindowResize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
}
import { useEffect } from 'react';
import { ThreeMapViewer as ThreeMapViewerImpl } from './src/three_map_viewer';
/**
* Three.js map viewer component
*/
export function ThreeMapViewer() {
useEffect(() => {
const viewer = new ThreeMapViewerImpl('.three-viewer');
window.onresize = () => viewer.onWindowResize();
return () => viewer.destroy();
}, []);
return <div className="three-viewer" style={{ width: '100vw', height: '100vh' }}></div>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment