App.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <template>
  2. <div id="root">
  3. <header
  4. role="banner"
  5. class="header-banner"
  6. @click.stop="onClickSiteTitle"
  7. @keyup.enter="onClickSiteTitle"
  8. >
  9. <div class="wrapper">
  10. <h2>Muntadas</h2>
  11. <h1
  12. class="site-title"
  13. tabindex="0"
  14. >
  15. Media Architecture Projects <sup>ALPHA</sup>
  16. </h1>
  17. </div>
  18. </header>
  19. <section role="main-content">
  20. <div class="wrapper">
  21. <renderer :obj="myRenderer" :size="size">
  22. <scene :obj="myScene" @update:obj="handleScene">
  23. <!-- <SkyBox :size="{x:500,y:500,z:500}" :position="{x:0,y:0,z:0}" :color="0x992222" /> -->
  24. <!-- <orbit-controls ref="cam_controls" :position="init_cam_pos" :rotation="{ x: 0, y: 0, z: 0 }">
  25. <camera />
  26. </orbit-controls> -->
  27. <camera ref="camera" :position="init_cam_pos" :rotation="init_cam_rot" />
  28. <!-- <light :hex="0xffffff" :intensity="5" :position="{x:-100,y:150,z:50}" />
  29. <light :hex="0xffffff" :intensity="2" :position="{x:100,y:-150,z:-50}" :options="light_opts" /> -->
  30. <animation :fn="animate" :speed="3" />
  31. <template v-if="debug">
  32. <cube :size="{x:30,y:1,z:1}" :position="{x:15,y:0,z:0}" :color="0x992222" />
  33. <cube :size="{x:1,y:30,z:1}" :position="{x:0,y:15,z:0}" :color="0x00BBFF" />
  34. <cube :size="{x:1,y:1,z:30}" :position="{x:0,y:0,z:15}" :color="0x17d100" />
  35. </template>
  36. <ground :size="{w:5000,h:5000}" :position="{x:0,y:0,z:0}" :color="0x133f52" :rotation="{x:90,y:0,z:0}" />
  37. <template v-if="projects.length">
  38. <project
  39. v-for="(project) in projects"
  40. :id="project.id"
  41. :key="project.id"
  42. @lift="onLift"
  43. />
  44. </template>
  45. </scene>
  46. </renderer>
  47. <Content v-if="content_data" :data="content_data" @onClose="onCloseContent" />
  48. <div
  49. v-if="modale_title && !content_data"
  50. class="modale-title"
  51. :style="{ bottom: modale_title_pos.y+'px', right: modale_title_pos.x+'px' }"
  52. >
  53. <span class="title">{{ modale_title }}</span>
  54. </div>
  55. </div>
  56. </section>
  57. <footer />
  58. </div>
  59. </template>
  60. <script>
  61. import { mapState, mapActions } from 'vuex'
  62. import mixins from 'components/mixins'
  63. import * as THREE from 'three'
  64. import TWEEN from '@tweenjs/tween.js'
  65. import BgVertex from 'assets/glsl/BgVertex'
  66. import SkyFragment from 'assets/glsl/SkyFragment'
  67. import WaterFragment from 'assets/glsl/WaterFragment'
  68. import Ground from './components/objects/Ground'
  69. import Cube from './components/objects/Cube'
  70. import Project from './components/objects/Project'
  71. import Content from './components/Content'
  72. // const TWEEN = require('@tweenjs/tween.js')
  73. const _debug = false
  74. export default {
  75. metaInfo: {
  76. // if no subcomponents specify a metaInfo.title, this title will be used
  77. title: 'Home',
  78. // all titles will be injected into this template
  79. titleTemplate: '%s | Muntadas'
  80. },
  81. components: {
  82. Cube,
  83. Project,
  84. Ground,
  85. Content
  86. },
  87. mixins: [mixins],
  88. data () {
  89. // const envcolor = 0xffffff
  90. let renderer = new THREE.WebGLRenderer({ alpha: false, antialias: true })
  91. renderer.setClearColor(0x000000, 0)
  92. // renderer.shadowMap.enabled = true
  93. // // to antialias the shadow
  94. // renderer.shadowMap.type = THREE.PCFSoftShadowMap
  95. // scene obj is well overwrited but background color not visible
  96. let scene = new THREE.Scene()
  97. scene.background = new THREE.Color(0x1a1a1a)
  98. // scene.fog = new THREE.Fog(new THREE.Color(0xffffff), 50, 200)
  99. // console.log('myScene', scene)
  100. // scene.add(new THREE.AxesHelper(500))
  101. // SKYDOME
  102. // https://github.com/mrdoob/three.js/blob/master/examples/webgl_lights_hemisphere.html
  103. // https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders--cms-23313
  104. var uniforms = {
  105. 'topColor': { value: new THREE.Color(0x0077ff) },
  106. 'horizontColor': { value: new THREE.Color(0xffffff) },
  107. 'waterColor': { value: new THREE.Color(0x13719a) },
  108. 'bottomColor': { value: new THREE.Color(0x000000) },
  109. 'offset': { value: 33 },
  110. 'wateroffset': { value: 33 },
  111. 'exponent': { value: 0.6 },
  112. 'waterexponent': { value: 0.6 }
  113. }
  114. // SphereBufferGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)
  115. var bgGeo = new THREE.SphereBufferGeometry(700, 32, 15, 0, 2 * Math.PI, 0, 0.5 * Math.PI)
  116. var skyMat = new THREE.ShaderMaterial({
  117. uniforms: uniforms,
  118. vertexShader: BgVertex,
  119. fragmentShader: SkyFragment,
  120. side: THREE.BackSide
  121. })
  122. var sky = new THREE.Mesh(bgGeo, skyMat)
  123. scene.add(sky)
  124. var waterMat = new THREE.ShaderMaterial({
  125. uniforms: uniforms,
  126. vertexShader: BgVertex,
  127. fragmentShader: WaterFragment,
  128. side: THREE.BackSide
  129. })
  130. var water = new THREE.Mesh(bgGeo, waterMat)
  131. water.rotateZ(this.deg2rad(180))
  132. scene.add(water)
  133. // SEA
  134. // lights
  135. var ambient = new THREE.AmbientLight(0xfcfcf9, 1)
  136. scene.add(ambient)
  137. // var sun = new THREE.PointLight(0xfcfcf9, 1)
  138. // // var sun = new THREE.DirectionalLight(0xfcfcf9, 2)
  139. // sun.position.set(-150, 200, 110)
  140. // // sun.castShadow = true
  141. // // sun.shadow.bias = -0.0001
  142. // // sun.shadow.mapSize.width = 1024 * 4
  143. // // sun.shadow.mapSize.height = 1024 * 4
  144. // // sun.target.position.set(0, 0, 0)
  145. // scene.add(sun)
  146. // // scene.add(sun.target)
  147. // var sun2 = new THREE.PointLight(0xfcfcf9, 1)
  148. // sun2.position.set(150, -200, -110)
  149. // // sun2.castShadow = true
  150. // // sun2.target.position.set(0, 0, 0)
  151. // scene.add(sun2)
  152. // // scene.add(sun2.target)
  153. //
  154. // var sun3 = new THREE.PointLight(0xfcfcf9, 0.6)
  155. // sun3.position.set(0, 150, -110)
  156. // // sun3.castShadow = true
  157. // // sun3.target.position.set(0, 0, 0)
  158. // scene.add(sun3)
  159. // // scene.add(sun2.target)
  160. //
  161. // var sun4 = new THREE.PointLight(0xfcfcf9, 0.6)
  162. // sun4.position.set(0, -150, -110)
  163. // // sun4.castShadow = true
  164. // // sun4.target.position.set(0, 0, 0)
  165. // scene.add(sun4)
  166. // // scene.add(sun2.target)
  167. // RETURN DATA
  168. return {
  169. debug: _debug,
  170. myRenderer: renderer,
  171. myScene: scene,
  172. // mouse_start: new THREE.Vector2(),
  173. // mouse: new THREE.Vector2(),
  174. camera: null,
  175. modale_title_pos: { x: 0, y: 0 },
  176. mouse: new THREE.Vector2(),
  177. controls: {
  178. user_interact: false,
  179. is_dragging: false,
  180. mouse: new THREE.Vector2(),
  181. mouse_start: new THREE.Vector2(),
  182. lon: -90,
  183. lat: -5.73,
  184. lon_start: 0,
  185. lat_start: 0,
  186. phi: 0,
  187. theta: 0,
  188. cam_target: new THREE.Vector3(0, 0, 0)
  189. },
  190. // cam_controls: null,
  191. init_cam_pos: { x: 0, y: 10, z: 100 },
  192. // init_cam_pos: { x: 0, y: 150, z: 0 },
  193. init_cam_rot: { x: this.deg2rad(180), y: 0, z: 0 },
  194. raycaster: new THREE.Raycaster(),
  195. interactive_objects_names: ['Project', 'Content'],
  196. interactive_objects: [],
  197. // light_opts: {
  198. // castShadow: true
  199. // },
  200. opened_vnode: null,
  201. content_data: null,
  202. modale_title: null
  203. }
  204. },
  205. computed: {
  206. ...mapState({
  207. projects: state => state.Projects.projects
  208. }),
  209. size () {
  210. return {
  211. w: window.innerWidth,
  212. h: window.innerHeight
  213. }
  214. }
  215. },
  216. // watch: {
  217. // projects (n, o) {
  218. // console.log(`watch projects new, old`, n, o)
  219. // }
  220. // },
  221. created () {
  222. if (!this.projects.length) {
  223. this.loadProjects()
  224. }
  225. this.$env3d.scene = this.myScene
  226. this.$env3d.renderer = this.myRenderer
  227. },
  228. mounted () {
  229. console.log('App mounted', this)
  230. console.log('App mounted $env3d', this.$env3d)
  231. this.camera = this.$env3d.camera = this.$refs.camera.curObj
  232. // this.cam_controls = this.$refs.cam_controls.controls
  233. // console.log('cam_controls', this.cam_controls)
  234. // this.cam_controls = new THREE.PointerLockControls(this.camera)
  235. // console.log('this.cam_controls', this.cam_controls)
  236. this.updatedInteractiveObjects()
  237. // CONTROLS
  238. // https://blogs.perficient.com/2020/05/21/3d-camera-movement-in-three-js-i-learned-the-hard-way-so-you-dont-have-to/
  239. this.myRenderer.domElement.addEventListener('mousedown', this.onDocMouseDown)
  240. this.myRenderer.domElement.addEventListener('mousemove', this.onDocMouseMove)
  241. this.myRenderer.domElement.addEventListener('mouseup', this.onDocMouseup)
  242. },
  243. updated () {
  244. this.updatedInteractiveObjects()
  245. },
  246. methods: {
  247. ...mapActions({
  248. loadProjects: 'Projects/loadProjects'
  249. }),
  250. handleScene (scene) {
  251. console.log('handlescene', scene)
  252. },
  253. updatedInteractiveObjects () {
  254. console.log('updatedInteractiveObjects', this.myScene.children)
  255. this.interactive_objects = []
  256. for (var i = 0; i < this.myScene.children.length; i++) {
  257. if (this.interactive_objects_names.indexOf(this.myScene.children[i].name) !== -1) {
  258. this.interactive_objects.push(this.myScene.children[i])
  259. }
  260. }
  261. // console.log('this.interactive_objects', this.interactive_objects)
  262. },
  263. onDocMouseDown (e) {
  264. // CONTROLS
  265. this.controls.mouse_start.x = e.clientX
  266. this.controls.mouse_start.y = e.clientY
  267. this.controls.lon_start = this.controls.lon
  268. this.controls.lat_start = this.controls.lat
  269. this.controls.user_interact = true
  270. },
  271. onDocMouseMove (e) {
  272. this.modale_title_pos = {
  273. x: window.innerWidth - e.clientX,
  274. y: window.innerHeight - e.clientY
  275. }
  276. // RAY CASTING : update the mouse variable
  277. this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1
  278. this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1
  279. // CONTROLS
  280. if (this.controls.user_interact) {
  281. this.controls.lon = (this.controls.mouse_start.x - e.clientX) * 0.1 + this.controls.lon_start
  282. this.controls.lat = (e.clientY - this.controls.mouse_start.y) * 0.1 + this.controls.lat_start
  283. // console.log(`lon: ${this.controls.lon}, lat: ${this.controls.lat}`)
  284. }
  285. // update the picking ray with the camera and mouse position
  286. this.raycaster.setFromCamera(this.mouse, this.camera)
  287. // calculate objects intersecting the picking ray
  288. var intersects = this.raycaster.intersectObjects(this.interactive_objects)
  289. if (intersects.length) {
  290. let object = intersects[0].object
  291. if (object.name === 'Content') {
  292. let vnode = object.userData.vnode
  293. console.log('Name', vnode.data.Name)
  294. this.modale_title = vnode.data.Name
  295. } else {
  296. this.modale_title = null
  297. }
  298. } else {
  299. this.modale_title = null
  300. }
  301. },
  302. onDocMouseup (e) {
  303. console.log('onDocumentMouseup', e)
  304. // CONTROLS
  305. this.controls.user_interact = false
  306. // // check if event is not a classic html link
  307. // // TODO: stopPropagation instead of that (but it does not work)
  308. // if (e.target.className === 'close-btn' ||
  309. // e.target.className === 'btn-level' ||
  310. // e.target.className === 'header-banner') {
  311. // // console.log('close-btn: vnode', this.opened_vnode)
  312. // // this.opened_vnode.isOpened = false
  313. // return
  314. // }
  315. // INTERACTIONS
  316. this.updatedInteractiveObjects()
  317. // check if not draging
  318. if (Math.hypot(e.clientX - this.controls.mouse_start.x, e.clientY - this.controls.mouse_start.y) < 5) {
  319. // update the picking ray with the camera and mouse position
  320. this.raycaster.setFromCamera(this.mouse, this.camera)
  321. // calculate objects intersecting the picking ray
  322. var intersects = this.raycaster.intersectObjects(this.interactive_objects)
  323. // console.log('intersects', intersects)
  324. // if we clicked on 3Dobject
  325. if (intersects.length) {
  326. let cam = this.camera
  327. // console.log('onDocMouseUp intersects', intersects)
  328. let object = intersects[0].object
  329. console.log('object[0]', object)
  330. let vnode = object.userData.vnode
  331. if (object.name === 'Content' && vnode.isOpened) {
  332. this.content_data = vnode.data
  333. }
  334. // close precedent vnode
  335. if (this.opened_vnode && this.opened_vnode.$options._componentTag === 'ContentBlock') {
  336. // console.log('opened_vnode', this.opened_vnode)
  337. this.opened_vnode.isOpened = false
  338. }
  339. // open current vnode
  340. // vnode.isOpened = true
  341. vnode.open()
  342. // recorde vnode as precedent vnode
  343. this.opened_vnode = vnode
  344. // get new camera target pos
  345. let toPos, tweenTime
  346. if (object.name === 'Content') {
  347. toPos = { ...object.position }
  348. tweenTime = 3000
  349. this.controls.lat = 0
  350. switch (vnode.face) {
  351. case 'left':
  352. toPos.x += 2.5
  353. this.controls.lon = 180
  354. break
  355. case 'back':
  356. toPos.z += 2.5
  357. this.controls.lon = -90
  358. break
  359. case 'right':
  360. toPos.x -= 2.5
  361. this.controls.lon = 0
  362. break
  363. case 'front':
  364. toPos.z -= 2.5
  365. this.controls.lon = 90
  366. break
  367. }
  368. // let controls = this.controls
  369. // let rad2deg = this.rad2deg
  370. // let camTarget = { ...this.controls.cam_target }
  371. new TWEEN.Tween(this.controls.cam_target)
  372. .easing(TWEEN.Easing.Quadratic.InOut)
  373. .to(object.position, 2000)
  374. .start()
  375. } else if (object.name === 'Project') {
  376. toPos = { ...object.userData.position }
  377. toPos.y = 2
  378. tweenTime = 7000
  379. }
  380. // toPos.y = 5
  381. let camPos = { ...this.camera.position }
  382. new TWEEN.Tween(camPos)
  383. .easing(TWEEN.Easing.Quadratic.InOut)
  384. .to(toPos, tweenTime)
  385. .onUpdate(function () {
  386. // console.log('tween update', this._object)
  387. cam.position.set(this._object.x, this._object.y, this._object.z)
  388. })
  389. .start()
  390. // console.log('tween', tween)
  391. // break
  392. // }
  393. } else {
  394. // new TWEEN.Tween(this.camera.position)
  395. // .to(this.init_cam_pos, 1000)
  396. // .start()
  397. }
  398. } // end if dragging
  399. },
  400. onClickSiteTitle (e) {
  401. e.preventDefault()
  402. let cam = this.camera
  403. let camPos = { ...this.camera.position }
  404. new TWEEN.Tween(camPos)
  405. .easing(TWEEN.Easing.Quadratic.InOut)
  406. .to(this.init_cam_pos, 5000)
  407. .onUpdate(function () {
  408. // console.log('tween update', this._object)
  409. cam.position.set(this._object.x, this._object.y, this._object.z)
  410. })
  411. .start()
  412. this.controls.lon = -90
  413. this.controls.lat = -5.75
  414. new TWEEN.Tween(this.controls.cam_target)
  415. .easing(TWEEN.Easing.Quadratic.InOut)
  416. .to({ x: 0, y: 0, z: 0 }, 5000)
  417. .start()
  418. // let camRot = { ...this.camera.rotation }
  419. // console.log('camRot', camRot)
  420. // new TWEEN.Tween(camRot)
  421. // .easing(TWEEN.Easing.Quadratic.InOut)
  422. // .to(this.init_cam_rot, 5000)
  423. // .onUpdate(function () {
  424. // // console.log('tween update', this._object)
  425. // cam.rotation.x = this._object.x
  426. // cam.rotation.y = this._object.y
  427. // cam.rotation.z = this._object.z
  428. // })
  429. // .start()
  430. },
  431. onLift (toPos) {
  432. console.log('onLift', toPos)
  433. let cam = this.camera
  434. let camPos = { ...cam.position }
  435. new TWEEN.Tween(camPos)
  436. .easing(TWEEN.Easing.Quadratic.InOut)
  437. .to(toPos, 3000)
  438. .onUpdate(function () {
  439. // console.log('tween update', this._object)
  440. cam.position.set(this._object.x, this._object.y, this._object.z)
  441. })
  442. .start()
  443. },
  444. animate (tt) {
  445. // CONTROLS
  446. if (this.controls.user_interact) {
  447. this.controls.lat = Math.max(-25, Math.min(45, this.controls.lat))
  448. this.controls.phi = this.deg2rad(90 - this.controls.lat)
  449. this.controls.theta = this.deg2rad(this.controls.lon)
  450. // if (this.controls.user_interact === false) {
  451. // this.controls.lon += 0.1
  452. // }
  453. this.controls.cam_target.x = 500 * Math.sin(this.controls.phi) * Math.cos(this.controls.theta)
  454. this.controls.cam_target.y = 500 * Math.cos(this.controls.phi)
  455. this.controls.cam_target.z = 500 * Math.sin(this.controls.phi) * Math.sin(this.controls.theta)
  456. }
  457. if (this.camera) {
  458. this.camera.lookAt(this.controls.cam_target)
  459. }
  460. // TWEENS
  461. TWEEN.update()
  462. },
  463. onCloseContent () {
  464. this.content_data = null
  465. }
  466. }
  467. }
  468. </script>
  469. <style lang="scss" scoped>
  470. .container{
  471. // font-family:'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
  472. max-width: 1200px;
  473. }
  474. </style>