App.vue 15 KB

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