-
-
Save dirvuk/e539bc738c65aff69a0c77fefa7f4436 to your computer and use it in GitHub Desktop.
Hackaton NASA 18
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Hackaton NASA 18 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:host { | |
overflow: hidden; | |
width: 100%; | |
height: 100%; | |
display: flex; | |
} | |
.model { | |
width: 100%; | |
height: 100%; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<canvas #canvas class="model" (click)="onClick($event)"></canvas> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Component, | |
ElementRef, | |
EventEmitter, | |
HostListener, | |
OnInit, | |
Output, | |
ViewChild, | |
Input | |
} from '@angular/core'; | |
import { Object3D, TextureLoader, Group, TGALoader, MeshPhongMaterial, CanvasTexture, Vector3 } from 'three'; | |
import * as THREE from 'three'; | |
import './js/EnableThreeExamples'; | |
import 'three/examples/js/loaders/OBJLoader'; | |
import 'three/examples/js/loaders/MTLLoader'; | |
import 'three/examples/js/loaders/TDSLoader'; | |
import 'three/examples/js/loaders/TGALoader'; | |
import 'three/examples/js/controls/OrbitControls'; | |
import 'three/examples/js/controls/TrackballControls.js'; | |
import { ThreeDModel } from '../models/3d.model'; | |
@Component({ | |
selector: 'app-scene', | |
templateUrl: './app-scene.component.html', | |
styleUrls: ['./app-scene.component.css'] | |
}) | |
export class AppSceneComponent implements OnInit { | |
_selectedModel: ThreeDModel; | |
@Input() | |
set selectedModel(value: ThreeDModel) { | |
this._selectedModel = value; | |
this.changeModel(); | |
} | |
get selectedModel(): ThreeDModel { | |
return this._selectedModel; | |
} | |
@Input() selectedLogo: ThreeDModel; | |
@Input() imageSize: number; | |
@Output() clickIntersect = new EventEmitter<Object3D>(); | |
@ViewChild('canvas') private canvasRef: ElementRef; | |
private renderer: THREE.WebGLRenderer; | |
private camera: THREE.PerspectiveCamera; | |
public advertCanvas: HTMLCanvasElement; | |
public scene: THREE.Scene; | |
public controls: THREE.OrbitControls; | |
private currentObject: Object3D; | |
private object: Object3D; | |
private loader; | |
public color: THREE.Color; | |
public texture: THREE.CanvasTexture; | |
private normalLoader: TextureLoader; | |
private textureLoader: TGALoader; | |
public fieldOfView = 60; | |
public nearClippingPane = 1; | |
public farClippingPane = 1100; | |
public canvasSize = 4096; | |
private animationTimer; | |
backgroundScene: THREE.Scene; | |
backgroundCamera: THREE.Camera; | |
/* LIFECYCLE */ | |
ngOnInit() { | |
window['a'] = this; | |
this.createBackground(); | |
this.createCamera(); | |
this.createLight(); | |
this.startRendering(); | |
this.addControls(); | |
} | |
createBackground(): any { | |
const texture = THREE.ImageUtils.loadTexture('assets/background.jpg'); | |
const backgroundMesh = new THREE.Mesh( | |
new THREE.PlaneGeometry(4, 4, 0), | |
new THREE.MeshBasicMaterial({ | |
map: texture | |
})); | |
(backgroundMesh .material as any).depthTest = false; | |
(backgroundMesh .material as any).depthWrite = false; | |
// Create your background scene | |
this.backgroundScene = new THREE.Scene(); | |
this.backgroundCamera = new THREE.Camera(); | |
this.backgroundScene.add(this.backgroundCamera); | |
this.backgroundScene.add(backgroundMesh); | |
} | |
private get canvas(): HTMLCanvasElement { | |
return this.canvasRef.nativeElement; | |
} | |
private changeModel() { | |
if (!this.selectedModel) { | |
return; | |
} | |
this.initNewScene(); | |
let obj; | |
if (obj = this.scene.getObjectByName('currObj')) { | |
obj.parent.remove(obj); | |
this.animationTimer = null; | |
this.render(); | |
} | |
// if (this.selectedModel.name === 'Astronaut') { | |
// this.animationTimer = null; | |
// this.createSce(); | |
// return; | |
// } | |
if (this.selectedModel.name === 'Astronaut') { | |
this.animationTimer = null; | |
this.createAstronaut2(); | |
return; | |
} | |
if (this.selectedModel.name === 'Arian-1') { | |
this.animationTimer = null; | |
this.createRocket(); | |
return; | |
} | |
this.createScene(); | |
} | |
private createRocket() { | |
let texture; | |
const manager = new THREE.LoadingManager(() => { }); | |
this.textureLoader = new THREE.TextureLoader(manager); | |
texture = this.textureLoader.load('assets/rocket1/rocket.jpg'); | |
this.loader = new (THREE as any).MTLLoader(); | |
this.loader.setPath(`assets/rocket1/`); | |
this.loader.load(`12217_rocket_v1_l1.mtl`, e => { | |
e.preload(); | |
new (THREE as any).OBJLoader() | |
.setPath('assets/rocket1/') | |
.load('12217_rocket_v1_l1.obj', (object) => { | |
this.object = object; | |
window['r'] = this.object; | |
this.object.position.set(0, -5.5, 0); | |
this.object.scale.set(0.01, 0.01, 0.01); | |
this.object.receiveShadow = false; | |
this.animationTimer = this.startObjectAnimation(this.object); | |
// this.camera.lookAt(this.object.position); | |
this.object.traverse(child => { | |
this.addCanvasTexture(child, this.object, texture); | |
}); | |
this.object.name = 'currObj'; | |
this.scene.add(this.object); | |
this.render(); | |
}, x => { }, err => { }); | |
}); | |
} | |
private createSce() { | |
this.loader = new (THREE as any).TDSLoader(); | |
this.loader.setResourcePath(`assets/astronaut/textures/`); | |
this.loader.load(`assets/astronaut/astronaut.3ds`, e => this.onAstroLoading(e)); | |
this.textureLoader = new THREE.TGALoader(); | |
this.render(); | |
} | |
private createAstronaut2() { | |
new (THREE as any).OBJLoader() | |
.setPath('assets/astronaut2/') | |
.load('astronaut2.obj', (object) => { | |
this.object = object; | |
window['e'] = this.object; | |
this.object.traverse(child => { | |
this.addCanvasTexture(child, this.object, texture); | |
}); | |
this.object.position.set(0, -7, 0); | |
this.object.receiveShadow = false; | |
this.object.scale.set(8, 8, 8); | |
this.animationTimer = this.startObjectAnimation(this.object); | |
this.object.name = 'currObj'; | |
this.scene.add(this.object); | |
this.render(); | |
}, x => { }, err => { }); | |
let texture; | |
const manager = new THREE.LoadingManager(() => { }); | |
this.textureLoader = new THREE.TextureLoader(manager); | |
texture = this.textureLoader.load('assets/astronaut2/z2_Color_s.jpg'); | |
// this.loader = new (THREE as any).MTLLoader(); | |
// this.loader.setPath(`assets/astronaut2/`); | |
// this.loader.load(`astronaut2.mtl`, e => { | |
// e.preload(); | |
// }); | |
} | |
startObjectAnimation(object: Object3D) { | |
return setInterval(() => { | |
object.rotateY(0.1); | |
this.render(); | |
}, 100); | |
} | |
private onAstroLoading(collada: Group) { | |
collada.children.forEach((e: THREE.Mesh) => { | |
const text = this.textureLoader.load('assets/astronaut/textures/astnt1_1.tga'); | |
this.addCanvasTexture(e, this.object, text); | |
}); | |
collada.name = 'currObj'; | |
this.scene.add(collada); | |
} | |
private createScene() { | |
this.loader = new (THREE as any).TDSLoader(); | |
this.normalLoader = new TextureLoader(); | |
const normal = this.normalLoader.load(`assets/${this.selectedModel.path}/textures/${this.selectedModel.normal}`); | |
this.loader.setResourcePath(`assets/${this.selectedModel.path}/textures/`); | |
this.loader.load(`assets/${this.selectedModel.path}/${this.selectedModel.path}.3ds`, | |
e => this.onModelLoadingCompleted(e, normal)); | |
} | |
private initNewScene() { | |
this.scene = new THREE.Scene(); | |
// this.scene.add(new THREE.AxesHelper(200)); | |
const light = new THREE.HemisphereLight(); | |
this.scene.add(light); | |
if (this.camera) { | |
this.scene.add(this.camera); | |
} | |
} | |
private onModelLoadingCompleted(collada, normal) { | |
this.currentObject = collada; | |
this.currentObject.traverse(object => { | |
this.addCanvasTexture(object, this.currentObject, normal, true); | |
}); | |
this.currentObject.receiveShadow = false; | |
this.currentObject.name = 'currObj'; | |
this.scene.add(this.currentObject); | |
this.animationTimer = this.startObjectAnimation(this.currentObject); | |
this.render(); | |
} | |
private setupCanvas(): void { | |
this.advertCanvas = document.createElement('canvas'); | |
this.advertCanvas.width = this.canvasSize; | |
this.advertCanvas.height = this.canvasSize; | |
this.color = new THREE.Color(); | |
this.texture = new THREE.CanvasTexture(this.advertCanvas); | |
} | |
private addCanvasTexture(object, currentObject, normal, isNormal = false): void { | |
this.setupCanvas(); | |
const child = object as any; | |
if (!object.isMesh && !child.material) { | |
return; | |
} | |
isNormal ? child.material.normalMap = normal : child.material.map = normal; | |
child.material.lightMap = normal; | |
currentObject.add(new THREE.Mesh(child.geometry, new MeshPhongMaterial({ | |
map: this.texture, | |
alphaTest: 0.5 | |
}))); | |
} | |
private createLight() { | |
// var directionalLight = new THREE.DirectionalLight( 0xffeedd ); | |
// directionalLight.position.set( 0, 0, 2 ); | |
// this.scene.add( directionalLight ); | |
} | |
private createCamera() { | |
const aspectRatio = this.getAspectRatio(); | |
this.camera = new THREE.PerspectiveCamera( | |
this.fieldOfView, | |
aspectRatio, | |
this.nearClippingPane, | |
this.farClippingPane | |
); | |
/** | |
* x: -13.51642127467603 | |
y: 0.20744722870848967 | |
z: -19.82593506593228 | |
*/ | |
// Set position and look at | |
this.camera.position.x = -10; | |
this.camera.position.y = 0.75; | |
this.camera.position.z = -17; | |
this.camera.rotation.x = -2; | |
this.camera.rotation.y = 0; | |
this.camera.rotation.z = -2; | |
} | |
private getAspectRatio(): number { | |
const height = this.canvas.clientHeight; | |
if (height === 0) { | |
return 0; | |
} | |
return this.canvas.clientWidth / this.canvas.clientHeight; | |
} | |
private startRendering() { | |
this.renderer = new THREE.WebGLRenderer({ | |
canvas: this.canvas, | |
antialias: true | |
}); | |
this.renderer.setPixelRatio(devicePixelRatio); | |
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight); | |
this.renderer.shadowMap.enabled = true; | |
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
this.renderer.setClearColor(0x1B1B1B, 1); | |
this.renderer.autoClear = true; | |
this.render(); | |
} | |
public render() { | |
if (this.renderer && this.scene) { | |
this.renderer.autoClear = false; | |
this.renderer.clear(); | |
this.renderer.render(this.backgroundScene , this.backgroundCamera); | |
this.renderer.render(this.scene, this.camera); | |
} | |
} | |
public addControls() { | |
this.controls = new THREE.OrbitControls(this.camera); | |
this.controls.rotateSpeed = 1.0; | |
this.controls.zoomSpeed = 1.2; | |
this.controls.addEventListener('change', () => this.render()); | |
} | |
/* EVENTS */ | |
public onClick(event: MouseEvent) { | |
// Example of mesh selection/pick: | |
const raycaster = new THREE.Raycaster(); | |
const mouse = new THREE.Vector2(); | |
mouse.x = ((event.clientX - 300) / this.renderer.domElement.clientWidth) * 2 - 1; | |
mouse.y = - (event.clientY / this.renderer.domElement.clientHeight) * 2 + 1; | |
raycaster.setFromCamera(mouse, this.camera); | |
const obj: THREE.Object3D[] = []; | |
this.findAllObjects(obj, this.scene); | |
const intersects = raycaster.intersectObjects(obj); | |
// console.log('Scene has ' + obj.length + ' objects'); | |
// console.log(intersects.length + ' intersected objects found'); | |
if (intersects && intersects.length) { | |
intersects.forEach((intersect, index) => { | |
if (index > 1) { | |
return; | |
} | |
// console.log(intersect); | |
const object = intersect.object; | |
if (intersect.uv && object && (object as any).material && (object as any).material.map instanceof CanvasTexture) { | |
this.addCanvasObject({ | |
offsetX: intersect.uv.x * this.canvasSize, | |
offsetY: (1 - intersect.uv.y) * this.canvasSize | |
}); | |
} | |
this.clickIntersect.emit(object); | |
}); | |
} | |
} | |
public addCanvasObject(event: { offsetX: number, offsetY: number }): void { | |
if (!this.selectedLogo) { | |
return; | |
} | |
const ctx = this.advertCanvas.getContext('2d'); | |
const image = new Image(750, 750); | |
image.src = this.selectedLogo.path; | |
ctx.drawImage(image, | |
event.offsetX - this.imageSize / 2, | |
event.offsetY - this.imageSize / 2, | |
this.imageSize, this.imageSize | |
); | |
this.texture.needsUpdate = true; | |
this.render(); | |
} | |
private findAllObjects(pred: THREE.Object3D[], parent: THREE.Object3D) { | |
// NOTE: Better to keep separate array of selected objects | |
if (parent.children.length > 0) { | |
parent.children.forEach((i) => { | |
pred.push(i); | |
this.findAllObjects(pred, i); | |
}); | |
} | |
} | |
@HostListener('window:resize', ['$event']) | |
public onResize() { | |
this.canvas.style.width = '100%'; | |
this.canvas.style.height = '100%'; | |
// console.log('onResize: ' + this.canvas.clientWidth + ', ' + this.canvas.clientHeight); | |
this.camera.aspect = this.getAspectRatio(); | |
this.camera.updateProjectionMatrix(); | |
this.renderer.setSize(this.canvas.clientWidth, this.canvas.clientHeight); | |
this.render(); | |
} | |
public splitByMaterial(geometry, materials) { | |
const parts = []; | |
const faces = ['a', 'b', 'c']; | |
let geo, vMap, iMat; | |
geometry.faces.forEach(face => { | |
if (face.materialIndex !== iMat) { | |
if (iMat) { | |
this.addPart(materials, iMat, parts, geo); | |
} | |
geo = new THREE.Geometry(); | |
vMap = {}; | |
iMat = face.materialIndex; | |
} | |
const f = face.clone(); | |
faces.forEach(p => { | |
const iv = face[p]; | |
if (!vMap.hasOwnProperty(iv)) { | |
vMap[iv] = geo.vertices.push(geometry.vertices[iv]) - 1; | |
} | |
f[p] = vMap[iv]; | |
}); | |
geo.faces.push(f); | |
}); | |
this.addPart(materials, iMat, parts, geo); | |
return parts; | |
} | |
private addPart(materials, iMat, parts, geo): void { | |
const mat = materials ? Object.assign({}, materials[iMat]) : {}; | |
mat.side = THREE.DoubleSide; | |
parts.push(new THREE.Mesh(geo, mat)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
THREE=require('three'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment