Skip to content

Instantly share code, notes, and snippets.

@mathdoodle
Created July 7, 2020 18:11
Show Gist options
  • Save mathdoodle/dca3c8deb6ee6cfed75f903f08474609 to your computer and use it in GitHub Desktop.
Save mathdoodle/dca3c8deb6ee6cfed75f903f08474609 to your computer and use it in GitHub Desktop.
Getting Started with three.js

Getting Started with three.js

This example uses ES6 modules to load the three.js library.

<!DOCTYPE html>
<html>
<head>
<base href="/">
<script src="https://unpkg.com/systemjs@0.21.6/dist/system.js"></script>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<div id="three" style="position: absolute; left:0px; top:0px"></div>
<script>
System.import('./index.js')
</script>
</body>
</html>
import { Scene } from 'three'
import { PerspectiveCamera } from 'three'
import { WebGLRenderer } from 'three'
import { PointLight, AmbientLight } from 'three'
import { ImageUtils, RepeatWrapping } from 'three'
import { Material } from 'three'
import { MeshBasicMaterial, BackSide, DoubleSide } from 'three'
import { MeshFaceMaterial } from 'three'
import { MeshLambertMaterial } from 'three'
import { PlaneGeometry } from 'three'
import { Mesh } from 'three'
import { AxesHelper } from 'three'
import { CubeGeometry, SphereGeometry } from 'three'
//
//
//
import { OrbitControls } from './OrbitControls'
import { WindowResize } from './WindowResize'
let scene: Scene
let camera: PerspectiveCamera
let renderer: WebGLRenderer
let container: HTMLElement
let stats: Stats
let controls: OrbitControls
DomReady.ready(main)
function main() {
init()
animate()
}
function init() {
scene = new Scene()
camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 20000)
scene.add(camera)
camera.position.set(0, 150, 400)
camera.lookAt(scene.position)
renderer = new WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
container = document.getElementById('three') as HTMLDivElement
container.appendChild(renderer.domElement)
WindowResize(renderer, camera)
controls = new OrbitControls(camera, renderer.domElement)
stats = new Stats()
stats.domElement.style.position = 'absolute'
stats.domElement.style.bottom = '0px'
stats.domElement.style.zIndex = '100'
container.appendChild(stats.domElement)
const pointLight = new PointLight(0xffffff)
pointLight.position.set(0, 250, 0)
scene.add(pointLight)
const ambientLight = new AmbientLight(0x111111)
scene.add(ambientLight)
const floorTexture = ImageUtils.loadTexture('assets/img/textures/misc/checkerboard.jpg')
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping
floorTexture.repeat.set(10, 10)
const floorMaterial = new MeshBasicMaterial({ map: floorTexture, side: DoubleSide })
const floorGeometry = new PlaneGeometry(1000, 1000, 10, 10)
const floor = new Mesh(floorGeometry, floorMaterial)
floor.position.y = -0.5
floor.rotation.x = Math.PI / 2
scene.add(floor)
const skyBoxGeometry = new CubeGeometry(10000, 10000, 10000)
const skyBoxMaterial = new MeshBasicMaterial({ color: 0x9999ff, side: BackSide })
const skyBox = new Mesh(skyBoxGeometry, skyBoxMaterial)
scene.add(skyBox)
const geometry = new SphereGeometry(50, 32, 16)
const material = new MeshLambertMaterial({ color: 0x8888ff })
const sphere = new Mesh(geometry, material)
sphere.position.set(100, 50, -50)
scene.add(sphere)
const cubeMaterials: Material[] = []
cubeMaterials.push(new MeshBasicMaterial({ color: 0xff3333 }))
cubeMaterials.push(new MeshBasicMaterial({ color: 0xff8800 }))
cubeMaterials.push(new MeshBasicMaterial({ color: 0xffff33 }))
cubeMaterials.push(new MeshBasicMaterial({ color: 0x33ff33 }))
cubeMaterials.push(new MeshBasicMaterial({ color: 0x3333ff }))
cubeMaterials.push(new MeshBasicMaterial({ color: 0x8833ff }))
const cubeMaterial = new MeshFaceMaterial(cubeMaterials)
const cubeGeometry = new CubeGeometry(100, 100, 100, 1, 1, 1)
const cube = new Mesh(cubeGeometry, cubeMaterial)
cube.position.set(-100, 50, -50)
scene.add(cube)
const axes = new AxesHelper(100)
axes.position.set(0, 50, 0)
scene.add(axes)
}
function animate() {
stats.begin()
renderer.render(scene, camera)
controls.update()
stats.end()
requestAnimationFrame(animate)
}
import { Object3D } from 'three'
import { Vector2, Vector3 } from 'three'
/**
* @author qiao / https://github.com/qiao
* @author mrdoob / http://mrdoob.com
* @author alteredq / http://alteredqualia.com/
* @author WestLangley / http://github.com/WestLangley
* @author David Geo Holmes (TypeScript migration)
*/
const EPS = 0.000001
// 65 /*A*/, 83 /*S*/, 68 /*D*/
const keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 }
export class OrbitControls {
private object: Object3D
private domElement: HTMLCanvasElement | Document
public enabled = true
public center = new Vector3()
public userZoom = true
public userZoomSpeed = 1.0
public userRotate = true
public userRotateSpeed = 1.0
public userPan = true
public userPanSpeed = 2.0
public autoRotate = false
public autoRotateSpeed = 2.0 // 30 seconds per round when fps is 60
public minPolarAngle = 0 // radians
public maxPolarAngle = Math.PI // radians
public minDistance = 0
public maxDistance = Infinity
private phiDelta = 0
private thetaDelta = 0
private scale = 1
private lastPosition = new Vector3()
constructor(object: Object3D, domElement: HTMLCanvasElement) {
this.object = object
this.domElement = (domElement !== undefined) ? domElement : document
const PIXELS_PER_ROUND = 1800
const rotateStart = new Vector2()
const rotateEnd = new Vector2()
const rotateDelta = new Vector2()
const zoomStart = new Vector2()
const zoomEnd = new Vector2()
const zoomDelta = new Vector2()
const STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 }
let state = STATE.NONE
// events
// var changeEvent = { type: 'change' };
this.domElement.addEventListener('contextmenu', function(event: Event) { event.preventDefault() }, false)
const onMouseDown = (event: MouseEvent) => {
if (this.enabled === false) return
if (this.userRotate === false) return
event.preventDefault()
if (state === STATE.NONE) {
if (event.button === 0)
state = STATE.ROTATE
if (event.button === 1)
state = STATE.ZOOM
if (event.button === 2)
state = STATE.PAN
}
if (state === STATE.ROTATE) {
// state = STATE.ROTATE;
rotateStart.set(event.clientX, event.clientY)
}
else if (state === STATE.ZOOM) {
// state = STATE.ZOOM;
zoomStart.set(event.clientX, event.clientY)
}
else if (state === STATE.PAN) {
// state = STATE.PAN;
}
document.addEventListener('mousemove', onMouseMove, false)
document.addEventListener('mouseup', onMouseUp, false)
}
this.domElement.addEventListener('mousedown', onMouseDown, false)
const onMouseMove = (event: MouseEvent) => {
if (this.enabled === false) return
event.preventDefault()
if (state === STATE.ROTATE) {
rotateEnd.set(event.clientX, event.clientY)
rotateDelta.subVectors(rotateEnd, rotateStart)
this.rotateLeft(2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * this.userRotateSpeed)
this.rotateUp(2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * this.userRotateSpeed)
rotateStart.copy(rotateEnd)
}
else if (state === STATE.ZOOM) {
zoomEnd.set(event.clientX, event.clientY)
zoomDelta.subVectors(zoomEnd, zoomStart)
if (zoomDelta.y > 0) {
this.zoomIn()
}
else {
this.zoomOut()
}
zoomStart.copy(zoomEnd)
} else if (state === STATE.PAN) {
const movementX: number = event['movementX'] || event['mozMovementX'] || event['webkitMovementX'] || 0
const movementY = event['movementY'] || event['mozMovementY'] || event['webkitMovementY'] || 0
this.pan(new Vector3(- movementX, movementY, 0))
}
}
const onMouseUp = (_event: MouseEvent) => {
if (this.enabled === false) return
if (this.userRotate === false) return
document.removeEventListener('mousemove', onMouseMove, false)
document.removeEventListener('mouseup', onMouseUp, false)
state = STATE.NONE
}
const onMouseWheel = (event: MouseWheelEvent) => {
if (this.enabled === false) return
if (this.userZoom === false) return
let delta = 0
if (event.wheelDelta) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta
}
else if (event.detail) { // Firefox
delta = - event.detail
}
if (delta > 0) {
this.zoomOut()
}
else {
this.zoomIn()
}
}
this.domElement.addEventListener('mousewheel', onMouseWheel, false)
this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false) // firefox
const onKeyDown = (event: KeyboardEvent) => {
if (this.enabled === false) return
if (this.userPan === false) return
switch (event.keyCode) {
/*case scope.keys.UP:
scope.pan( new THREE.Vector3( 0, 1, 0 ) );
break;
case scope.keys.BOTTOM:
scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
break;
case scope.keys.LEFT:
scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
break;
case scope.keys.RIGHT:
scope.pan( new THREE.Vector3( 1, 0, 0 ) );
break;
*/
case keys.ROTATE:
state = STATE.ROTATE
break
case keys.ZOOM:
state = STATE.ZOOM
break
case keys.PAN:
state = STATE.PAN
break
}
}
const onKeyUp = (event: KeyboardEvent) => {
switch (event.keyCode) {
case keys.ROTATE:
case keys.ZOOM:
case keys.PAN:
state = STATE.NONE
break
}
}
window.addEventListener('keydown', onKeyDown, false)
window.addEventListener('keyup', onKeyUp, false)
}
private getAutoRotationAngle(): number {
return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed
}
private getZoomScale(): number {
return Math.pow(0.95, this.userZoomSpeed)
}
rotateLeft(angle?: number): void {
if (angle === undefined) {
angle = this.getAutoRotationAngle()
}
this.thetaDelta -= angle
}
rotateRight(angle?: number): void {
if (angle === undefined) {
angle = this.getAutoRotationAngle()
}
this.thetaDelta += angle
}
rotateUp(angle?: number) {
if (angle === undefined) {
angle = this.getAutoRotationAngle()
}
this.phiDelta -= angle
}
rotateDown(angle?: number) {
if (angle === undefined) {
angle = this.getAutoRotationAngle()
}
this.phiDelta += angle
}
zoomIn(zoomScale?: number): void {
if (zoomScale === undefined) {
zoomScale = this.getZoomScale()
}
this.scale /= zoomScale
}
zoomOut(zoomScale?: number): void {
if (zoomScale === undefined) {
zoomScale = this.getZoomScale()
}
this.scale *= zoomScale
}
pan(distance: Vector3) {
distance.transformDirection(this.object.matrix)
distance.multiplyScalar(this.userPanSpeed)
this.object.position.add(distance)
this.center.add(distance)
}
update() {
const position = this.object.position
const offset = position.clone().sub(this.center)
// angle from z-axis around y-axis
let theta = Math.atan2(offset.x, offset.z)
// angle from y-axis
let phi = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y)
if (this.autoRotate) {
this.rotateLeft(this.getAutoRotationAngle())
}
theta += this.thetaDelta
phi += this.phiDelta
// restrict phi to be between desired limits
phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi))
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max(EPS, Math.min(Math.PI - EPS, phi))
let radius = offset.length() * this.scale
// restrict radius to be between desired limits
radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius))
offset.x = radius * Math.sin(phi) * Math.sin(theta)
offset.y = radius * Math.cos(phi)
offset.z = radius * Math.sin(phi) * Math.cos(theta)
position.copy(this.center).add(offset)
this.object.lookAt(this.center)
this.thetaDelta = 0
this.phiDelta = 0
this.scale = 1
if (this.lastPosition.distanceTo(this.object.position) > 0) {
// this.dispatchEvent( changeEvent )
this.lastPosition.copy(this.object.position)
}
}
}
{
"description": "Getting Started with three.js",
"name": "copy-of-three.js with ES6 modules",
"version": "1.0.0",
"dependencies": {
"DomReady": "1.0.0",
"stats.js": "0.16.0",
"three": "0.117.1"
},
"keywords": [
"THREE",
"three.js",
"stemcstudio",
"ES6 modules"
],
"linting": true,
"hideConfigFiles": true,
"author": "David Geo Holmes"
}
body {
background: blue;
}
{
"allowJs": true,
"checkJs": true,
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"jsx": "react",
"module": "system",
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"traceResolution": true
}
{
"rules": {
"array-type": [
true,
"array"
],
"curly": false,
"comment-format": [
true,
"check-space"
],
"eofline": true,
"forin": true,
"jsdoc-format": true,
"new-parens": true,
"no-conditional-assignment": false,
"no-consecutive-blank-lines": true,
"no-construct": true,
"no-for-in-array": true,
"no-inferrable-types": [
true
],
"no-magic-numbers": false,
"no-shadowed-variable": true,
"no-string-throw": true,
"no-trailing-whitespace": [
true,
"ignore-jsdoc"
],
"no-var-keyword": true,
"one-variable-per-declaration": [
true,
"ignore-for-loop"
],
"prefer-const": true,
"prefer-for-of": true,
"prefer-function-over-method": false,
"prefer-method-signature": true,
"radix": true,
"semicolon": [
true,
"never"
],
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": true,
"use-isnan": true
}
}
import { WebGLRenderer } from 'three'
import { PerspectiveCamera } from 'three'
// This helper makes it easy to handle window resize.
// It will update renderer and camera when window is resized.
//
// # Usage
//
// **Step 1**: Start updating renderer and camera
//
// ```var windowResize = WindowResize(aRenderer, aCamera)```
//
// **Step 2**: Start updating renderer and camera
//
// ```windowResize.stop()```
// # Code
/**
* Update renderer and camera when the window is resized
*
* @param {Object} renderer the renderer to update
* @param {Object} Camera the camera to update
*/
export function WindowResize(renderer: WebGLRenderer, camera: PerspectiveCamera) {
const callback = function() {
// notify the renderer of the size change
renderer.setSize(window.innerWidth, window.innerHeight)
// update the camera
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
}
// bind the resize event
window.addEventListener('resize', callback, false)
// return .stop() the function to stop watching window resize
return {
/**
* Stop watching window resize
*/
stop: function() {
window.removeEventListener('resize', callback)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment