SceneをまとめたものをChapterと定義する
イベント処理にはPub/Subを採用
pubsub-jsの使い方
http://qiita.com/mo4_9/items/2ac77bdc7399600bc2d4
処理の流れ
- ChapterManager.js - setChapter | 現在のChapterを決定
- ChapterManager.js - handleClick | 現在のChapterのhandleClick()を実行
- Chapter1.js - PubSub.publish | クリックされたオブジェクトに応じてイベントを発行
- ChapterManager.js - PubSub.subscribe | 購読中のイベントに通知
Chapter*.js
renderを含まない各Chapterを用意
Chapter1.js
, Chapter2.js
, Chapter3.js
./lib/Chapter1.js
import PubSub from 'pubsub-js';
export default class Chapter1 {
constructor(opts = {}) {
this.TOPIC_NAMESPACE = 'Chapter1';
this.width = opts.width || window.innerWidth;
this.height = opts.height || window.innerHeight;
this.init();
}
init() {
{ // scene
this.scene = new THREE.Scene();
}
{ // camera
this.camera = new THREE.PerspectiveCamera( 45, this.width / this.height, 1, 10000 ); // fov(視野角),aspect,near,far
this.camera.position.set(100, 100, 100);
this.camera.up.set(0, 1, 0);
this.camera.lookAt({ x:0, y:0, z:0 });
}
{ // controls
this.controls = new THREE.OrbitControls(this.camera);
}
{ // lights
const ambientLight = new THREE.AmbientLight(0xffffff);
this.scene.add(ambientLight);
}
{ // axis
const axis = new THREE.AxisHelper(1000);
axis.position.set(0,0,0);
this.scene.add(axis);
}
{ // grid
const grid = new THREE.GridHelper(50, 10); // size, step
this.scene.add(grid);
}
{ // cube
const cgeometry = new THREE.CubeGeometry(20, 20, 20);
const cmaterial = new THREE.MeshLambertMaterial({ color: 0xffcc66 });
this.cube = new THREE.Mesh(cgeometry, cmaterial);
this.cube.name = `cube-${this.TOPIC_NAMESPACE}`;
this.scene.add(this.cube);
}
{ // sphere
const sgeometry = new THREE.SphereGeometry(10, 30, 30);
const smaterial = new THREE.MeshLambertMaterial({ color: 0x66ccff });
this.sphere = new THREE.Mesh(sgeometry, smaterial);
this.sphere.name = `sphere-${this.TOPIC_NAMESPACE}`;
this.sphere.position.set(10,50,-10);
this.scene.add(this.sphere);
}
}
handleClick(objs) {
// クリックされたオブジェクトに応じて、発行するイベントを分岐する
const isCubeClick = objs.some(obj => obj.object.name == 'cube-Chapter1'); // nameで判定
const isSphereClick = objs.some(obj => obj.object == this.sphere); // objectで判定
console.log(objs, isCubeClick, isSphereClick);
if (isCubeClick) {
PubSub.publish(`nextObjClick.${this.TOPIC_NAMESPACE}`);
}
if (isSphereClick) {
PubSub.publish(`prevObjClick.${this.TOPIC_NAMESPACE}`);
}
}
}
ChapterManager.js
./lib/ChapterManager.js
import PubSub from 'pubsub-js';
import './OrbitControls';
import Chapter1 from './Chapter1';
import Chapter2 from './Chapter2';
import Chapter3 from './Chapter3';
export default class ChapterManager{
constructor(opts = {}) {
this.RENDER_INTERVAL = 30;
this.TICK_INTERVAL = 1500;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.container = opts.container || document.createElement('div');
this.chapterList = [
new Chapter1({
width: this.width,
height: this.height,
}),
new Chapter2({
width: this.width,
height: this.height,
}),
new Chapter3({
width: this.width,
height: this.height,
}),
];
this.init();
this.setEvent();
this.setChapter();
}
init() {
{ // renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setClearColor( 0x222222 ); // 背景色
this.renderer.setPixelRatio(window.devicePixelRatio || 1);
this.renderer.setSize( this.width, this.height );
this.container.appendChild( this.renderer.domElement );
}
}
setEvent() {
const rect = this.renderer.domElement.getBoundingClientRect();
this.renderer.domElement.addEventListener('mousedown', (evt) => {
const mouseX = evt.clientX - rect.left;
const mouseY = evt.clientY - rect.top;
const position = {
x: (mouseX / rect.width) * 2 - 1, // -1 ~ +1 の座標
y: -(mouseY / rect.height) * 2 + 1, // -1 ~ +1 の座標
};
const objs = this.getIntersectObjects(position.x, position.y);
if (objs.length > 0) {
if (!this.currentChapter.handleClick) {
return;
}
this.currentChapter.handleClick(objs);
}
}, false);
PubSub.subscribe('nextObjClick.Chapter1', () => {
this.setChapter(1);
});
PubSub.subscribe('prevObjClick.Chapter1', () => {
this.setChapter(2);
});
PubSub.subscribe('nextObjClick.Chapter2', () => {
this.setChapter(2);
});
PubSub.subscribe('prevObjClick.Chapter2', () => {
this.setChapter(0);
});
PubSub.subscribe('nextObjClick.Chapter3', () => {
this.setChapter(0);
});
PubSub.subscribe('prevObjClick.Chapter3', () => {
this.setChapter(1);
});
}
// sceneとcameraを現在のchapterに更新する
setChapter(id = 0) {
this.currentChapter = this.chapterList[id];
console.log(this.currentChapter);
this.scene = this.currentChapter.scene;
console.log(this.scene);
this.camera = this.currentChapter.camera;
}
getIntersectObjects(x, y) {
const vector = new THREE.Vector3(x, y, 1);
vector.unproject(this.camera);
const ray = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
const objs = ray.intersectObjects(this.scene.children);
return objs;
}
start() {
const startTime = Date.now();
let previousTime = startTime;
let previousRenderTime = previousTime;
let previousTickTime = previousTime;
this.loopId;
const loop = (timestamp) => {
const nowTime = Date.now();
const elapsedTime = nowTime - startTime;
const deltaTime = nowTime - previousTime;
const deltaRenderTime = nowTime - previousRenderTime;
const deltaTickTime = nowTime - previousTickTime;
if (deltaRenderTime > this.RENDER_INTERVAL) {
previousRenderTime = nowTime;
this.render();
}
if (deltaTickTime > this.TICK_INTERVAL) {
previousTickTime = nowTime;
this.tick();
}
previousTime = nowTime;
this.loopId = requestAnimationFrame(loop);
};
loop();
}
stop() {
cancelAnimationFrame(this.loopId);
}
tick() {
}
render() {
this.renderer.render( this.scene, this.camera );
}
}
script.js
import ChapterManager from './lib/ChapterManager';
(() => {
const chapterManager = new ChapterManager({
container: document.getElementById('canvas-container'),
});
chapterManager.start();
})();