406 lines
13 KiB
Vue
406 lines
13 KiB
Vue
<template>
|
|
<div id="root">
|
|
<header role="banner">
|
|
<div class="wrapper">
|
|
<h1
|
|
class="site-title"
|
|
tabindex="0"
|
|
>
|
|
Media Architecture Projects <sup>ALPHA</sup>
|
|
</h1>
|
|
<h2>Antoni Muntadas</h2>
|
|
</div>
|
|
</header>
|
|
<section role="main-content">
|
|
<div class="wrapper">
|
|
<renderer :obj="myRenderer" :size="size">
|
|
<scene :obj="myScene" @update:obj="handleScene">
|
|
|
|
<!-- <SkyBox :size="{x:500,y:500,z:500}" :position="{x:0,y:0,z:0}" :color="0x992222" /> -->
|
|
|
|
<!-- <orbit-controls ref="cam_controls" :position="init_cam_pos" :rotation="{ x: 0, y: 0, z: 0 }">
|
|
<camera />
|
|
</orbit-controls> -->
|
|
<camera ref="camera" :position="init_cam_pos" :rotation="init_cam_rot" />
|
|
|
|
<!-- <light :hex="0xffffff" :intensity="5" :position="{x:-100,y:150,z:50}" />
|
|
<light :hex="0xffffff" :intensity="2" :position="{x:100,y:-150,z:-50}" :options="light_opts" /> -->
|
|
|
|
<animation :fn="animate" :speed="3" />
|
|
|
|
<template v-if="debug">
|
|
<cube :size="{x:30,y:1,z:1}" :position="{x:15,y:0,z:0}" :color="0x992222" />
|
|
<cube :size="{x:1,y:30,z:1}" :position="{x:0,y:15,z:0}" :color="0x00BBFF" />
|
|
<cube :size="{x:1,y:1,z:30}" :position="{x:0,y:0,z:15}" :color="0x17d100" />
|
|
</template>
|
|
|
|
<ground :size="{w:5000,h:5000}" :position="{x:0,y:0,z:0}" :color="0x133f52" :rotation="{x:90,y:0,z:0}" />
|
|
|
|
<template v-if="projects.length">
|
|
<project
|
|
v-for="(project, index) in projects"
|
|
:key="project.id"
|
|
:data="project"
|
|
:len="projects.length"
|
|
:index="index"
|
|
/>
|
|
</template>
|
|
|
|
</scene>
|
|
</renderer>
|
|
<Content v-if="content_data" :data="content_data" @onClose="onCloseContent" />
|
|
</div>
|
|
</section>
|
|
<footer />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapState, mapActions } from 'vuex'
|
|
import mixins from 'components/mixins'
|
|
import * as THREE from 'three'
|
|
import TWEEN from '@tweenjs/tween.js'
|
|
|
|
import BgVertex from 'assets/glsl/BgVertex'
|
|
import SkyFragment from 'assets/glsl/SkyFragment'
|
|
import WaterFragment from 'assets/glsl/WaterFragment'
|
|
|
|
import Ground from './components/objects/Ground'
|
|
import Cube from './components/objects/Cube'
|
|
import Project from './components/objects/Project'
|
|
import Content from './components/Content'
|
|
|
|
// const TWEEN = require('@tweenjs/tween.js')
|
|
const _debug = false
|
|
|
|
export default {
|
|
metaInfo: {
|
|
// if no subcomponents specify a metaInfo.title, this title will be used
|
|
title: 'Home',
|
|
// all titles will be injected into this template
|
|
titleTemplate: '%s | Muntadas'
|
|
},
|
|
components: {
|
|
Cube,
|
|
Project,
|
|
Ground,
|
|
Content
|
|
},
|
|
mixins: [mixins],
|
|
data () {
|
|
// const envcolor = 0xffffff
|
|
let renderer = new THREE.WebGLRenderer({ alpha: false, antialias: true })
|
|
renderer.setClearColor(0x000000, 0)
|
|
renderer.shadowMap.enabled = true
|
|
|
|
// scene obj is well overwrited but background color not visible
|
|
let scene = new THREE.Scene()
|
|
scene.background = new THREE.Color(0x1a1a1a)
|
|
// scene.fog = new THREE.Fog(new THREE.Color(0xffffff), 50, 200)
|
|
// console.log('myScene', scene)
|
|
|
|
// SKYDOME
|
|
// https://github.com/mrdoob/three.js/blob/master/examples/webgl_lights_hemisphere.html
|
|
// https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders--cms-23313
|
|
var uniforms = {
|
|
'topColor': { value: new THREE.Color(0x0077ff) },
|
|
'horizontColor': { value: new THREE.Color(0xffffff) },
|
|
'waterColor': { value: new THREE.Color(0x13719a) },
|
|
'bottomColor': { value: new THREE.Color(0x000000) },
|
|
'offset': { value: 33 },
|
|
'wateroffset': { value: 33 },
|
|
'exponent': { value: 0.6 },
|
|
'waterexponent': { value: 0.6 }
|
|
}
|
|
// SphereBufferGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)
|
|
var bgGeo = new THREE.SphereBufferGeometry(900, 32, 15, 0, 2 * Math.PI, 0, 0.5 * Math.PI)
|
|
var skyMat = new THREE.ShaderMaterial({
|
|
uniforms: uniforms,
|
|
vertexShader: BgVertex,
|
|
fragmentShader: SkyFragment,
|
|
side: THREE.BackSide
|
|
})
|
|
var sky = new THREE.Mesh(bgGeo, skyMat)
|
|
scene.add(sky)
|
|
var waterMat = new THREE.ShaderMaterial({
|
|
uniforms: uniforms,
|
|
vertexShader: BgVertex,
|
|
fragmentShader: WaterFragment,
|
|
side: THREE.BackSide
|
|
})
|
|
var water = new THREE.Mesh(bgGeo, waterMat)
|
|
water.rotateZ(this.deg2rad(180))
|
|
scene.add(water)
|
|
|
|
// SEA
|
|
|
|
// lights
|
|
var sun = new THREE.PointLight(0xfcfcf9, 2)
|
|
sun.position.set(-150, 150, 110)
|
|
sun.castShadows = true
|
|
// sun.target.position.set(0, 0, 0)
|
|
scene.add(sun)
|
|
// scene.add(sun.target)
|
|
|
|
var sun2 = new THREE.PointLight(0xfcfcf9, 0.6)
|
|
sun2.position.set(150, 150, 110)
|
|
sun2.castShadows = true
|
|
// sun2.target.position.set(0, 0, 0)
|
|
scene.add(sun2)
|
|
// scene.add(sun2.target)
|
|
|
|
var sun3 = new THREE.PointLight(0xfcfcf9, 0.6)
|
|
sun3.position.set(0, 150, -110)
|
|
sun3.castShadows = true
|
|
// sun3.target.position.set(0, 0, 0)
|
|
scene.add(sun3)
|
|
// scene.add(sun2.target)
|
|
|
|
var sun4 = new THREE.PointLight(0xfcfcf9, 0.6)
|
|
sun4.position.set(0, -150, -110)
|
|
sun4.castShadows = true
|
|
// sun4.target.position.set(0, 0, 0)
|
|
scene.add(sun4)
|
|
// scene.add(sun2.target)
|
|
|
|
// RETURN DATA
|
|
return {
|
|
debug: _debug,
|
|
myRenderer: renderer,
|
|
myScene: scene,
|
|
// mouse_start: new THREE.Vector2(),
|
|
// mouse: new THREE.Vector2(),
|
|
camera: null,
|
|
mouse: new THREE.Vector2(),
|
|
controls: {
|
|
user_interact: false,
|
|
is_dragging: false,
|
|
mouse: new THREE.Vector2(),
|
|
mouse_start: new THREE.Vector2(),
|
|
lon: -91,
|
|
lat: 0,
|
|
lon_start: 0,
|
|
lat_start: 0,
|
|
phi: 0,
|
|
theta: 0,
|
|
cam_target: new THREE.Vector3(0, 0, 0)
|
|
},
|
|
// cam_controls: null,
|
|
init_cam_pos: { x: 0, y: 10, z: 100 },
|
|
// init_cam_pos: { x: 0, y: 150, z: 0 },
|
|
// init_cam_rot: { x: this.deg2rad(180), y: 0, z: 0 },
|
|
raycaster: new THREE.Raycaster(),
|
|
interactive_objects_names: ['Project', 'Content'],
|
|
interactive_objects: [],
|
|
light_opts: {
|
|
castShadow: true
|
|
},
|
|
opened_vnode: null,
|
|
content_data: null
|
|
}
|
|
},
|
|
computed: {
|
|
...mapState({
|
|
projects: state => state.Projects.projects
|
|
}),
|
|
size () {
|
|
return {
|
|
w: window.innerWidth,
|
|
h: window.innerHeight
|
|
}
|
|
}
|
|
},
|
|
watch: {
|
|
projects (n, o) {
|
|
console.log(`watch projects new, old`, n, o)
|
|
}
|
|
},
|
|
created () {
|
|
if (!this.projects.length) {
|
|
this.getProjects()
|
|
}
|
|
this.$env3d.scene = this.myScene
|
|
this.$env3d.renderer = this.myRenderer
|
|
},
|
|
mounted () {
|
|
console.log('App mounted', this)
|
|
console.log('App mounted $env3d', this.$env3d)
|
|
|
|
this.camera = this.$env3d.camera = this.$refs.camera.curObj
|
|
// this.cam_controls = this.$refs.cam_controls.controls
|
|
// console.log('cam_controls', this.cam_controls)
|
|
|
|
// this.cam_controls = new THREE.PointerLockControls(this.camera)
|
|
// console.log('this.cam_controls', this.cam_controls)
|
|
|
|
this.updatedInteractiveObjects()
|
|
// CONTROLS
|
|
// https://blogs.perficient.com/2020/05/21/3d-camera-movement-in-three-js-i-learned-the-hard-way-so-you-dont-have-to/
|
|
document.addEventListener('mousedown', this.onDocMouseDown, false)
|
|
document.addEventListener('mousemove', this.onDocMouseMove, false)
|
|
document.addEventListener('mouseup', this.onDocMouseup, false)
|
|
},
|
|
updated () {
|
|
this.updatedInteractiveObjects()
|
|
},
|
|
methods: {
|
|
...mapActions({
|
|
getProjects: 'Projects/getProjects'
|
|
}),
|
|
handleScene (scene) {
|
|
console.log('handlescene', scene)
|
|
},
|
|
updatedInteractiveObjects () {
|
|
console.log('updatedInteractiveObjects', this.myScene.children)
|
|
this.interactive_objects = []
|
|
for (var i = 0; i < this.myScene.children.length; i++) {
|
|
if (this.interactive_objects_names.indexOf(this.myScene.children[i].name) !== -1) {
|
|
this.interactive_objects.push(this.myScene.children[i])
|
|
}
|
|
}
|
|
// console.log('this.interactive_objects', this.interactive_objects)
|
|
},
|
|
onDocMouseDown (e) {
|
|
// CONTROLS
|
|
this.controls.mouse_start.x = e.clientX
|
|
this.controls.mouse_start.y = e.clientY
|
|
this.controls.lon_start = this.controls.lon
|
|
this.controls.lat_start = this.controls.lat
|
|
this.controls.user_interact = true
|
|
},
|
|
onDocMouseMove (e) {
|
|
// RAY CASTING : update the mouse variable
|
|
this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1
|
|
this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1
|
|
|
|
// CONTROLS
|
|
if (this.controls.user_interact) {
|
|
this.controls.lon = (this.controls.mouse_start.x - e.clientX) * 0.1 + this.controls.lon_start
|
|
this.controls.lat = (e.clientY - this.controls.mouse_start.y) * 0.1 + this.controls.lat_start
|
|
}
|
|
},
|
|
onDocMouseup (e) {
|
|
console.log('onDocumentMouseup', e)
|
|
// CONTROLS
|
|
this.controls.user_interact = false
|
|
|
|
// check if event is not a classic html link
|
|
if (e.target.className === 'close-btn') {
|
|
// console.log('close-btn: vnode', this.opened_vnode)
|
|
// this.opened_vnode.isOpened = false
|
|
return
|
|
}
|
|
|
|
// INTERACTIONS
|
|
this.updatedInteractiveObjects()
|
|
|
|
// check if draging
|
|
if (Math.hypot(e.clientX - this.controls.mouse_start.x, e.clientY - this.controls.mouse_start.y) < 5) {
|
|
// update the picking ray with the camera and mouse position
|
|
this.raycaster.setFromCamera(this.mouse, this.camera)
|
|
// calculate objects intersecting the picking ray
|
|
var intersects = this.raycaster.intersectObjects(this.interactive_objects)
|
|
// console.log('intersects', intersects)
|
|
|
|
let object, vnode, toPos
|
|
let cam = this.camera
|
|
let camPos = { ...this.camera.position }
|
|
if (intersects.length) {
|
|
// console.log('onDocMouseUp intersects', intersects)
|
|
// for (var i = 0; i < intersects.length; i++) {
|
|
object = intersects[0].object
|
|
vnode = object.userData.vnode
|
|
if (object.name === 'Content' && vnode.isOpened) {
|
|
this.content_data = vnode.data
|
|
}
|
|
|
|
// close precedent vnode
|
|
if (this.opened_vnode && this.opened_vnode.$options._componentTag === 'ContentBlock') {
|
|
// console.log('opened_vnode', this.opened_vnode)
|
|
this.opened_vnode.isOpened = false
|
|
}
|
|
// opene current vnode
|
|
vnode.isOpened = true
|
|
// recorde vnode as precedent vnode
|
|
this.opened_vnode = vnode
|
|
|
|
toPos = { ...object.position }
|
|
if (object.name === 'Content') {
|
|
// toPos.z += 1
|
|
switch (vnode.face) {
|
|
case 'left':
|
|
toPos.x += 4
|
|
break
|
|
case 'back':
|
|
toPos.z += 4
|
|
break
|
|
case 'right':
|
|
toPos.x -= 4
|
|
break
|
|
case 'front':
|
|
toPos.z -= 4
|
|
break
|
|
}
|
|
// TODO: we need to update lon and lat accordingly when chaging camera orientation
|
|
// new TWEEN.Tween(this.controls.cam_target)
|
|
// .to(object.position, 2000)
|
|
// .start()
|
|
}
|
|
// toPos.y = 5
|
|
new TWEEN.Tween(camPos)
|
|
.easing(TWEEN.Easing.Quadratic.InOut)
|
|
.to(toPos, 3000)
|
|
.onUpdate(function () {
|
|
// console.log('tween update', this._object)
|
|
cam.position.set(this._object.x, this._object.y, this._object.z)
|
|
})
|
|
.start()
|
|
|
|
// console.log('tween', tween)
|
|
// break
|
|
// }
|
|
} else {
|
|
// new TWEEN.Tween(this.camera.position)
|
|
// .to(this.init_cam_pos, 1000)
|
|
// .start()
|
|
}
|
|
} // end if dragging
|
|
},
|
|
animate (tt) {
|
|
// CONTROLS
|
|
this.controls.lat = Math.max(-85, Math.min(85, this.controls.lat))
|
|
this.controls.phi = this.deg2rad(90 - this.controls.lat)
|
|
this.controls.theta = this.deg2rad(this.controls.lon)
|
|
// if (this.controls.user_interact === false) {
|
|
// this.controls.lon += 0.1
|
|
// }
|
|
// if (this.controls.user_interact) {
|
|
this.controls.cam_target.x = 500 * Math.sin(this.controls.phi) * Math.cos(this.controls.theta)
|
|
this.controls.cam_target.y = 500 * Math.cos(this.controls.phi)
|
|
this.controls.cam_target.z = 500 * Math.sin(this.controls.phi) * Math.sin(this.controls.theta)
|
|
// }
|
|
|
|
if (this.camera) {
|
|
this.camera.lookAt(this.controls.cam_target)
|
|
}
|
|
|
|
// TWEENS
|
|
TWEEN.update()
|
|
},
|
|
onCloseContent () {
|
|
this.content_data = null
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.container{
|
|
// font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
|
|
max-width: 1200px;
|
|
|
|
}
|
|
|
|
</style>
|