Angularプロジェクトを作成する
プロジェクトを新規作成する
プロジェクトを作るフォルダを決める
shift + 右クリック
PowerShell ウインドウをここで開く
PowerShell でコマンドを打つ
ng new プロジェクト名
今回は、プロジェクト名を threeTest
としました
? ワークスペースでより厳密なタイプチェックとより厳密なバンドルバジェットを適用しますか?
この設定は、保守性を向上させ、バグを事前にキャッチするのに役立ちます。
詳細については、https://angular.io/strict(y / N)を参照してください。
→ y Yes
? Angularルーティングを追加しますか? (y / N)
→ y Yes
? どのスタイルシート形式を使用しますか?
→ SCSS
プロジェクトを起動してみる
必要なコンポーネントをインストールするコマンドを打つ
npm install
プロジェクトを起動するコマンドを打つ
ng serve --open
Three.js をプロジェクトに追加する
準備
外部のモジュールを追加
npm install three
npm install @types/three
npm install troika-three-text
npm install @three-ts/orbit-controls
プロジェクトにコンポーネントを追加
Three.js を表示するコンポーネント を追加する コマンドを打つ
ng g c three
Three.js をコントロールするサービス を追加する コマンドを打つ
ng g s three/scene
ソースコードを編集
a
scene.service.ts
import { Injectable } from '@angular/core';
import * as THREE from 'three';
import { OrbitControls } from '@three-ts/orbit-controls';
@Injectable({
providedIn: 'root'
})
export class SceneService {
// シーン
private scene: THREE.Scene;
// レンダラー
private renderer!: THREE.WebGLRenderer;
// カメラ
private camera!: THREE.PerspectiveCamera;
private aspectRatio: number = 0;
private Width: number = 0;
private Height: number = 0;
private GridHelper!: THREE.GridHelper;
// 初期化
public constructor() {
// シーンを作成
this.scene = new THREE.Scene();
// シーンの背景を白に設定
// this.scene.background = new THREE.Color(0xf0f0f0);
this.scene.background = new THREE.Color( 0xffffff );
// レンダラーをバインド
this.render = this.render.bind(this);
}
public OnInit(aspectRatio: number,
canvasElement: HTMLCanvasElement,
deviceRatio: number,
Width: number,
Height: number): void {
// カメラ
this.aspectRatio = aspectRatio;
this.Width = Width;
this.Height = Height;
this.createCamera(aspectRatio, Width, Height);
// 環境光源
this.add(new THREE.AmbientLight(0xf0f0f0));
// レンダラー
this.createRender(canvasElement,
deviceRatio,
Width,
Height);
// コントロール
this.addControls();
// 床面を生成する
this.createHelper();
}
// 床面を生成する
private createHelper() {
this.GridHelper = new THREE.GridHelper(200, 20);
this.GridHelper.geometry.rotateX(Math.PI / 2);
this.scene.add(this.GridHelper);
}
// コントロール
public addControls() {
const controls = new OrbitControls(this.camera, this.renderer.domElement);
controls.addEventListener('change', this.render);
}
// カメラの初期化
public createCamera(aspectRatio: number,
Width: number, Height: number ) {
aspectRatio = (aspectRatio === null) ? this.aspectRatio : aspectRatio;
Width = (Width === null) ? this.Width : Width;
Height = (Height === null) ? this.Height : Height;
const target = this.scene.getObjectByName('camera');
if (target !== undefined) {
this.scene.remove(this.camera);
}
this.camera = new THREE.PerspectiveCamera(
70,
aspectRatio,
0.1,
1000
);
this.camera.position.set(0, -50, 20);
this.camera.name = 'camera';
this.scene.add(this.camera);
}
// レンダラーを初期化する
public createRender(canvasElement: HTMLCanvasElement,
deviceRatio: number,
Width: number,
Height: number): void {
this.renderer = new THREE.WebGLRenderer({
preserveDrawingBuffer: true,
canvas: canvasElement,
alpha: true, // transparent background
antialias: true // smooth edges
});
this.renderer.setPixelRatio(deviceRatio);
this.renderer.setSize(Width, Height);
this.renderer.shadowMap.enabled = true;
}
// リサイズ
public onResize(deviceRatio: number,
Width: number,
Height: number): void {
this.camera.updateProjectionMatrix();
this.renderer.setSize(Width, Height);
this.render();
}
// レンダリングする
public render() {
this.renderer.render(this.scene, this.camera);
}
// レンダリングのサイズを取得する
public getBoundingClientRect(): ClientRect | DOMRect {
return this.renderer.domElement.getBoundingClientRect();
}
// シーンにオブジェクトを追加する
public add(...threeObject: THREE.Object3D[]): void {
for (const obj of threeObject) {
this.scene.add(obj);
}
}
// シーンのオブジェクトを削除する
public remove(...threeObject: THREE.Object3D[]): void {
for (const obj of threeObject) {
this.scene.remove(obj);
}
}
// シーンにオブジェクトを削除する
public removeByName(...threeName: string[]): void {
for (const name of threeName) {
const target = this.scene.getObjectByName(name);
if (target === undefined) {
continue;
}
this.scene.remove(target);
}
}
// ファイルに視点を保存する
public getSettingJson(): any {
return {
camera: {
x: this.camera.position.x,
y: this.camera.position.y,
z: this.camera.position.z,
}
};
}
}
three.component.html
<canvas #myCanvas id="myCanvas" style="overflow:hidden; position: absolute;" (mousedown)="onMouseDown($event)"
(mouseup)="onMouseUp($event)" (mousemove)="onMouseMove($event)">
</canvas>
three.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild, HostListener, NgZone, OnDestroy } from '@angular/core';
import * as THREE from 'three';
import { SceneService } from './scene.service';
@Component({
selector: 'app-three',
templateUrl: './three.component.html',
styleUrls: ['./three.component.scss'],
})
export class ThreeComponent implements AfterViewInit, OnDestroy {
@ViewChild('myCanvas', { static: true }) private canvasRef!: ElementRef;
constructor(private ngZone: NgZone,
private scene: SceneService) {
THREE.Object3D.DefaultUp.set(0, 0, 1);
}
private get canvas(): HTMLCanvasElement {
return this.canvasRef.nativeElement;
}
ngAfterViewInit() {
this.scene.OnInit(this.getAspectRatio(),
this.canvas,
devicePixelRatio,
window.innerWidth,
window.innerHeight);
// レンダリングする
this.animate();
}
ngOnDestroy() {
}
animate(): void {
// We have to run this outside angular zones,
// because it could trigger heavy changeDetection cycles.
this.ngZone.runOutsideAngular(() => {
window.addEventListener('DOMContentLoaded', () => {
this.scene.render();
});
});
}
// マウスクリック時のイベント
@HostListener('mousedown', ['$event'])
public onMouseDown(event: MouseEvent) {
}
// マウスクリック時のイベント
@HostListener('mouseup', ['$event'])
public onMouseUp(event: MouseEvent) {
}
// マウス移動時のイベント
@HostListener('mousemove', ['$event'])
public onMouseMove(event: MouseEvent) {
}
// ウインドウがリサイズした時のイベント処理
@HostListener('window:resize', ['$event'])
public onResize(event: Event) {
this.scene.onResize(this.getAspectRatio(),
window.innerWidth,
window.innerHeight - 120);
}
private getAspectRatio(): number {
if (this.canvas.clientHeight === 0) {
return 0;
}
return this.canvas.clientWidth / this.canvas.clientHeight;
}
}
サンプルファイル
完成形
https://github.com/sasaco/threeTest/archive/refs/heads/master.zip