threejsで大規模なモデル操作とか始めるとTypescriptの型サポートはとても助かる。
以前にも書いていたことがあったが、古かったので消して、代わりに2020年度時点の環境設定を残しておく。
package.json
{ "name": "threejs-tutorial", "version": "0.0.1", "description": "threejs-tutorial", "main": "src/index.ts", "scripts": { "build": "webpack --mode production", "dev": "webpack-dev-server", "start": "webpack-dev-server --open --mode development" }, "devDependencies": { "@types/dat.gui": "^0.7.5", "@types/three": "^0.103.2", "css-loader": "^3.5.3", "html-webpack-plugin": "^4.3.0", "style-loader": "^1.2.1", "ts-loader": "^7.0.4", "typescript": "^3.9.3", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0", "worker-plugin": "^4.0.3" }, "dependencies": { "dat.gui": "^0.7.7", "three": "^0.116.1" } }
tsconfig.json
Typescriptの設定。
{ "compilerOptions": { "target": "es2019", "module": "esnext", "moduleResolution": "node", "strict": true, "sourceMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true } }
webpack.config.js
webpackの設定。babelは使っていない。
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { app: './src/index.ts', }, devtool: 'inline-source-map', devServer: { contentBase: './dist' }, plugins: [ new HtmlWebpackPlugin({ title: 'threejs tutorial', meta: [ {viewport: 'width=device-width, initial-scale=1'}, ], }) ], module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }, { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }, resolve: { extensions: ['.tsx', '.ts', '.js'] }, output: { filename: '[name].bundle.js', path: path.resolve(__dirname, 'dist') }, };
src/index.ts
GLTFLoaderでglbのモデルを読み込んで、OrbitControlsでマウスでぐるぐるできるだけの簡単なサンプル。 このくらいだと型定義はほとんどないけど。
import * as THREE from 'three' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import './style.css'; function createRenderer() { const scene = new THREE.Scene(); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setClearColor(0x111111); renderer.setSize(window.innerWidth, window.innerHeight); const planeGeometory = new THREE.PlaneGeometry(60, 20); const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xcccccc }); const planeMesh = new THREE.Mesh(planeGeometory, planeMaterial); planeMesh.rotation.x = -0.5 * Math.PI; planeMesh.position.x = 15; planeMesh.position.y = 0; planeMesh.position.z = 0; scene.add(planeMesh); const camera = new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 0.1, 1000 ); camera.position.x = -30; camera.position.y = 40; camera.position.z = 30; camera.lookAt(scene.position); const light = new THREE.DirectionalLight(0xffffff); light.position.set(1, 1, 1).normalize(); scene.add(light); const loader = new GLTFLoader(); loader.load('./torus.glb', (glb) => { glb.scene.scale.set(10, 10, 10); scene.add(glb.scene); console.log('glb model loaded'); }); document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.update(); return () => renderer.render(scene, camera); } function startAnimation(updateView: Function) { const update = () => { requestAnimationFrame(update); updateView(); } update(); } window.addEventListener("DOMContentLoaded", () => { const updateView = createRenderer(); startAnimation(updateView); });
src/style.css
全画面表示のためのスタイル
body { margin: 0; overflow: hidden; }
あと、dist/torus.glbを適当につくって置いておけばOK。