├── .gitignore ├── .idea ├── inspectionProfiles │ └── Project_Default.xml └── vcs.xml ├── README.md ├── assets ├── 2-0.png ├── 2-1.png ├── b.jpg ├── c.jpg ├── d.jpg ├── niu.png ├── wx.png └── xigua.png ├── index.html └── libs ├── anime.min.js ├── css.min.css ├── deri.js ├── fastclick.js ├── imgs ├── 001_electric.jpg ├── book.jpg ├── bookall.png ├── icon-earth.png ├── icon-water.png ├── lightray_red.jpg ├── logo.png ├── page2back.png ├── photo.jpg ├── skybox │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ └── posz.jpg ├── up.jpg ├── users │ ├── niu.png │ └── xigua.png ├── water1.jpg └── water2.jpg ├── index.js ├── jquery-3.3.1.min.js ├── music ├── 0.mp3 └── 1.mp3 ├── three.min.js ├── three ├── CSS2DRenderer.js ├── CopyShader.js ├── DigitalGlitch.js ├── EffectComposer.js ├── FXAAShader.js ├── GlitchPass.js ├── OrbitControls.js ├── OutlinePass.js ├── Reflector.js ├── RenderPass.js ├── ShaderPass.js ├── THREE.MeshLine.js └── three.lib.js └── tween.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waterdrop 3D 2 | 3 | 这是一个关于《三体》水滴的网站
4 | 初版 做完了 5 | 6 | - 老夫就是要用 jquery 7 | - THREE.JS v92, IE11+以上支持 8 | - 页面排版 简单排了排,没有什么设计能力 9 | 10 | ## 地址 11 | 12 | 国际:https://isluo.com/work/water/ 13 | 14 | ## 关于《三体》 15 | 16 | 《三体》是刘慈欣 大刘的长篇科幻小说,共 3 部。获得了星云奖、雨果奖等国际奖项。 17 | 18 | > 初次看见《三体》时,是在上初三的时候,借的同学的《科幻世界》杂志,第 1 篇文章就是《三体》,看了一遍,发现讲的是文化大革命,耐着性子看完了第 1 集,根本没出现任何"科幻"的东西。
19 | > 十几年匆匆而过,直到我参加工作了,才重新想起三体,发现已经完结了。于是抄起手机就开始看。哇塞,我觉得这就是想象力的极限了,我沉浸其中无法自拔。
20 | > 人的一生是多么短暂呵,在那遥远的时间的终点,又会有怎样的光景呢。 21 | 22 | ## 版权及免责声明 23 | 24 | - 本网站是根据刘慈欣《三体》中的情节构建的,摘抄了部分原文。
25 | - 图片均来源于网络
26 | - 本网站仅作为我的个人作品,如有侵权请联系我。
27 | - 本网站源代码可供转发修改,但不得用于商业用途 28 |
29 |
30 |
31 | 32 |
33 |
34 | 35 | - 微信扫一扫上方二维码可小额赞赏我。 36 | - 赞赏任一金额,你的微信昵称将显示在本网站赞助者名单中 37 | - 所得资金不会用于帮助任何人类 38 | - 所有资金将用于流浪小猫小狗和自然环境保护计划(我会通过阿里巴巴公益或腾讯公益捐出去,会附上赞助者的昵称) 39 | - 资金流向及查询后续会公布在下方。 40 |


41 | 42 | ## 赞助者名单 43 | 44 | 滴滴香小单(水滴计划由蓝色空间号独家赞助)  45 | 科捷智能科技-葛晋 Momiji  46 | 卡斯特梅的雨  47 | T-800  48 | HK  49 | Hodor  50 | Alnitak(THE WARTERDROP)  51 | 佚名  52 | Logic  53 | 希望重燃.  54 | 岁寒(:white_square_button:)  55 | Rose Dry Leaf Gas(我们是同志了)  56 | 风(支持!)  57 | Asphyxia.(朝歌还会远吗?)  58 | 小灰和小白的铲屎官(牛牛牛罗老师)  59 | 朱昀栎  60 | CoderWangx(水滴很酷:thumbsup:)  61 | 欢乐正前方(加油!)  62 | meloalright  63 | 士大夫(大赞)  64 | 天将明(探索未知)  65 | 今天明天吃什么呢(专升本复习完善成个人笔记最好)  66 | 我的名字十二个字不信你数  67 | 把海弄干的鱼  68 | 傅 Fu(需要批发二向箔)  69 | Citrus  70 |   71 | 吴(消灭人类暴政,世界属于三体)  72 | 松涛(做的非常棒)  73 | moleQ(我又来了)  74 | 温度 ℃  75 | 企鹅丫丫 AVON  76 | 太苦特级方糖(希望捐给猫猫)  77 | 王宇晗  78 | ![niu](/assets/niu.png)  79 | 西瓜丸子![xigua](/assets/xigua.png)  80 | 氕氘氚  81 | 真羽  82 | 浮生六记(ETO 成员前来报告)  83 | Z(Hala Madrid)  84 | 小张  85 | Z(现实与幻想并存)  86 | Doggy(太牛了 woc)  87 | Mr O'G 桑  88 | Roland(消灭人类暴政,世界属于三体!)  89 | 23:33(太酷了)  90 | care(鑫哥,你真棒)  91 | 一雪君  92 | leooo  93 | 小朋友真好吃(么么哒)  94 | 邶陂以北(真就这些,不多,世界属于三体,跃迁中)  95 | \~科 24\~  96 | 呐-是小中(66)  97 | veer  98 | dragon.(加油鸭)  99 | 青城山下㇏(冲鸭!孟照森!)  100 | Thel'Vadamee(消灭人类暴政,世界属于三体!)  101 | 缪可  102 | 秦至宁(极光永不消逝!)  103 | 104 | 105 |


106 | (如果您看不到图片,可能需要科学上网)

107 | 108 | > 2018/06/25 给流浪狗狗一顿饱饭 (需手机上点击自动跳转支付宝) ¥ 12.01 109 | 110 | 111 | 112 | > 2018/09/22 《守护栖息地任鸟飞》支付宝公益“大自然保护”板块 ¥ 11.34 113 | 114 | 115 | 116 | > 2021/03/04 阿派流浪猫狗救助站 中国绿发会。¥ 120 117 | 118 | 119 | 120 | > 资金已全部捐出,虽然是以我的账号捐的,但由衷感谢所有赞助者。 121 | 122 | 123 | -------------------------------------------------------------------------------- /assets/2-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/2-0.png -------------------------------------------------------------------------------- /assets/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/2-1.png -------------------------------------------------------------------------------- /assets/b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/b.jpg -------------------------------------------------------------------------------- /assets/c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/c.jpg -------------------------------------------------------------------------------- /assets/d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/d.jpg -------------------------------------------------------------------------------- /assets/niu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/niu.png -------------------------------------------------------------------------------- /assets/wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/wx.png -------------------------------------------------------------------------------- /assets/xigua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/assets/xigua.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Waterdrop 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | T 15 | H 16 | R 17 | E 18 | E 19 |   20 | B 21 | O 22 | D 23 | Y 24 |   25 | W 26 | A 27 | T 28 | E 29 | R 30 | D 31 | R 32 | O 33 | P 34 |
35 |
36 |

在西边的天际,正在下沉的夕阳仿佛被融化着

37 |

如血在云海和太空中弥漫,映出一大片壮丽的红色

38 |

"这是人类的落日",叶文洁轻轻地说。

39 |
40 |
41 |
42 | 《三体》 43 |
44 |
45 | 46 |
47 |
48 | 56 |
57 |
0 %
58 |
59 |
60 |
启动
61 |
62 |
63 |
64 |
您现在可以自由移动视角
65 |
66 | 77 |
78 |
79 |
80 |
WATERDROP
81 |
公元2212年,人类捕获了来自半人马星座α星系三体文明的探测器,它被命名为水滴。
82 |
83 | waterdrop 84 |
85 |
水滴
86 |
由三体文明制造的小型空间穿梭机,因其外形与水滴相似而得名。全长3.5米,表面呈绝对光滑的全反射镜面。银河系在它的表面映成一片流畅的光纹,使得这滴水银看上去纯洁而唯美,它的液滴外形是那么栩栩如生,以至于观察者误以为它没有内部结构。
87 |
88 | waterdrop 89 |
90 |
强互作用力外壳
91 |
水滴的外壳由一层仅有一个原子厚的强互作用力材料构成,其原子被压缩到紧紧相连,保持绝对静止,连自身振动都消失了。所以水滴的外表面呈绝对零度,像一面镜子一样能够反射无限远的光线。水滴在发动机开机时会产生大量的热能,且强互作用力材料因为不存在分子运动所以是完全隔热的。
92 |
93 |
94 |
曲率驱动
95 |
96 | 指一种理论上的推进系统(theoretical propulsion system),能让航天器以光速飞行。宇宙的空间并不平坦,如果把宇宙的整体想象为一张大膜,这张膜的表面是弧形的,整张膜甚至可能是一个封闭的肥皂泡。 97 |

98 | 这种技术可用于制造光速飞船,使星际旅行成为可能。
99 |

100 |
101 |
"弱小和无知不是生存的障碍,傲慢才是"
102 |
103 |
104 |
105 |
SILENT SPRING
106 |
在中国,任何超脱飞扬的思想都会砰然坠地的
107 |
现实的引力太沉重了
108 |
109 | waterdrop 110 |
111 |
寂静的春天
112 |
113 | 大楼顶上出现了一个娇小的身影,那个美丽的女孩子挥动着一面“四·二八”的大旗,她的出现立刻招来了一阵杂乱的枪声,“四·二八”的成员以前多次玩过这个游戏,在楼顶上站出来的人,除了挥舞旗帜外,有时还用喇叭筒喊口号或向下撒传单,每次他们都能在弹雨中全身而退,为自己挣到了崇高的荣誉。 114 |

115 | 这次出来的女孩儿显然也相信自己还有那样的幸运,她挥舞着战旗,挥动着自己燃烧的青春,敌人将在这火焰中化为灰烬,理想世界明天就会在她那沸腾的热血中诞生……她陶醉在这鲜红灿烂的梦幻中,直到被一颗步枪子弹洞穿了胸膛,十五岁少女的胸膛是那么柔嫩,那颗子弹穿过后基本上没有减速,在她身后的空中发出一声啾鸣。 116 |

117 | 年轻的红卫兵同她的旗帜一起从楼顶落下,她那轻盈的身体落得甚至比旗帜还慢,仿佛小鸟眷恋着天空。 118 |

119 |
120 |
121 |
叶文洁
122 |
123 | 大学教授、天体物理学家、ETO统帅,人类毁灭的重要一环。 124 |

125 | 经历了文化大革命,其父坚持不肯向非理性的狂热屈服而被批斗致死,她在那个极端年代里的悲惨遭遇,使善良而温和的她对人性失去信心,人性之恶和社会愚昧丑陋的意识形态深深扎进了她的心中。对人类的彻底失望和无助让她感到只有借助外部的力量才能改变这种现实。叶文洁的一生是悲惨的,她也亲手铸造了一个悲剧。 126 |

127 |
128 |
129 |
黑暗森林法则
130 |
宇宙就是一座黑暗森林,每个文明都是带枪的猎人,像幽灵般潜行于林间,轻轻拨开挡路的树枝,竭力不让脚步发出一点儿声音,连呼吸都必须小心翼翼:他必须小心,因为林中到处都有与他一样潜行的猎人,如果他发现了别的生命,能做的只有一件事:开枪消灭之。在这片森林中,他人就是地狱,就是永恒的威胁,任何暴露自己存在的生命都将很快被消灭,这就是宇宙文明的图景,这就是对费米悖论的解释。
131 |
132 |
"这是人类的落日"
133 |
134 |
135 |
136 |
THREE BODY
137 |
死亡是唯一一座永远亮着的灯塔,不管你向哪里航行,最终都得转向它指引的方向
138 |
大多数人到死都没有向尘世之外瞥一眼
139 |
140 |
141 | threebody 142 |
《三体》
143 |
刘慈欣
144 |
145 |
146 |
三体问题
147 |
148 | 三体是天体力学名词,由三个质点和引力作用组成的力学关系,主要是指三颗质量相似的恒星在它们所组成的独立天体系统中,这三颗恒星的运动轨迹找不到任何规律。 149 |

150 | 在一般三体问题中,每一个天体在其他两个天体的万有引力作用下的运动方程都可以表示成3个二阶的常微分方程,或6个一阶的常微分方程。因此,一般三体问题的运动方程为十八阶方程,必须得到18个积分才能得到完全解。然而,现阶段还只能得到三体问题的10个初积分,还远不能解决三体问题。 151 |

152 |
153 |
154 | 155 |
156 |
刘慈欣 · 中国当代科幻第一人,工程师、作家。
157 |
凭借《三体》3部曲荣获星云奖、雨果奖、轨迹奖最佳长篇科幻小说。脑洞大开,带你领略想象力的极限
158 |
159 |
160 |
"送你一颗星星,我将在时间的尽头等你"
161 |
162 | 163 |
微信扫一扫上方二维码可小额赞助网站作者,你的微信昵称将出现在赞助者名单中。所获资金不会用于救助任何人类。将全部捐赠给流浪小猫小狗援助计划及自然环境保护活动(主要是阿里巴巴公益)。资金总额流向和查询会公布在下方
164 |
165 |
166 |
赞助者名单
167 |
    168 |
    总金额:¥0.00
    169 |
    资金流向查询:此页面地址最下面
    170 |
    171 | 175 | 176 |
    177 | 联系网站作者: 178 | 376693576@qq.com 179 |
    180 |
    181 |
    182 |
    183 |
    184 |
    185 |
    186 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /libs/anime.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | 2017 Julian Garnier 3 | Released under the MIT license 4 | */ 5 | var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(e,r,p){if(p.get||p.set)throw new TypeError("ES3 does not support getters and setters.");e!=Array.prototype&&e!=Object.prototype&&(e[r]=p.value)};$jscomp.getGlobal=function(e){return"undefined"!=typeof window&&window===e?e:"undefined"!=typeof global&&null!=global?global:e};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_"; 6 | $jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(e){return $jscomp.SYMBOL_PREFIX+(e||"")+$jscomp.symbolCounter_++}; 7 | $jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var e=$jscomp.global.Symbol.iterator;e||(e=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[e]&&$jscomp.defineProperty(Array.prototype,e,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(e){var r=0;return $jscomp.iteratorPrototype(function(){return rb&&(b+=1);1b?c:b<2/3?a+(c-a)*(2/3-b)*6:a}var d=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(a);a=parseInt(d[1])/360;var b=parseInt(d[2])/100,f=parseInt(d[3])/100,d=d[4]||1;if(0==b)f=b=a=f;else{var n=.5>f?f*(1+b):f+b-f*b,k=2*f-n,f=c(k,n,a+1/3),b=c(k,n,a);a=c(k,n,a-1/3)}return"rgba("+ 13 | 255*f+","+255*b+","+255*a+","+d+")"}function y(a){if(a=/([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(a))return a[2]}function V(a){if(-1=g.currentTime)for(var G=0;G=w||!k)g.began||(g.began=!0,f("begin")),f("run");if(q>n&&q=k&&r!==k||!k)b(k),x||e();f("update");a>=k&&(g.remaining?(t=h,"alternate"===g.direction&&(g.reversed=!g.reversed)):(g.pause(),g.completed||(g.completed=!0,f("complete"),"Promise"in window&&(p(),m=c()))),l=0)}a=void 0===a?{}:a;var h,t,l=0,p=null,m=c(),g=fa(a);g.reset=function(){var a=g.direction,c=g.loop;g.currentTime= 25 | 0;g.progress=0;g.paused=!0;g.began=!1;g.completed=!1;g.reversed="reverse"===a;g.remaining="alternate"===a&&1===c?2:c;b(0);for(a=g.children.length;a--;)g.children[a].reset()};g.tick=function(a){h=a;t||(t=h);k((l+h-t)*q.speed)};g.seek=function(a){k(d(a))};g.pause=function(){var a=v.indexOf(g);-1=c&&0<=b&&1>=b){var e=new Float32Array(11);if(c!==d||b!==f)for(var k=0;11>k;++k)e[k]=a(.1*k,c,b);return function(k){if(c===d&&b===f)return k;if(0===k)return 0;if(1===k)return 1;for(var h=0,l=1;10!==l&&e[l]<=k;++l)h+=.1;--l;var l=h+(k-e[l])/(e[l+1]-e[l])*.1,n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(.001<=n){for(h=0;4>h;++h){n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(0===n)break;var m=a(l,c,b)-k,l=l-m/n}k=l}else if(0=== 29 | n)k=l;else{var l=h,h=h+.1,g=0;do m=l+(h-l)/2,n=a(m,c,b)-k,0++g);k=m}return a(k,d,f)}}}}(),Q=function(){function a(a,b){return 0===a||1===a?a:-Math.pow(2,10*(a-1))*Math.sin(2*(a-1-b/(2*Math.PI)*Math.asin(1))*Math.PI/b)}var c="Quad Cubic Quart Quint Sine Expo Circ Back Elastic".split(" "),d={In:[[.55,.085,.68,.53],[.55,.055,.675,.19],[.895,.03,.685,.22],[.755,.05,.855,.06],[.47,0,.745,.715],[.95,.05,.795,.035],[.6,.04,.98,.335],[.6,-.28,.735,.045],a],Out:[[.25, 30 | .46,.45,.94],[.215,.61,.355,1],[.165,.84,.44,1],[.23,1,.32,1],[.39,.575,.565,1],[.19,1,.22,1],[.075,.82,.165,1],[.175,.885,.32,1.275],function(b,c){return 1-a(1-b,c)}],InOut:[[.455,.03,.515,.955],[.645,.045,.355,1],[.77,0,.175,1],[.86,0,.07,1],[.445,.05,.55,.95],[1,0,0,1],[.785,.135,.15,.86],[.68,-.55,.265,1.55],function(b,c){return.5>b?a(2*b,c)/2:1-a(-2*b+2,c)/2}]},b={linear:A(.25,.25,.75,.75)},f={},e;for(e in d)f.type=e,d[f.type].forEach(function(a){return function(d,f){b["ease"+a.type+c[f]]=h.fnc(d)? 31 | d:A.apply($jscomp$this,d)}}(f)),f={type:f.type};return b}(),ha={css:function(a,c,d){return a.style[c]=d},attribute:function(a,c,d){return a.setAttribute(c,d)},object:function(a,c,d){return a[c]=d},transform:function(a,c,d,b,f){b[f]||(b[f]=[]);b[f].push(c+"("+d+")")}},v=[],B=0,ia=function(){function a(){B=requestAnimationFrame(c)}function c(c){var b=v.length;if(b){for(var d=0;db&&(c.duration=d.duration);c.children.push(d)});c.seek(0);c.reset();c.autoplay&&c.restart();return c};return c};q.random=function(a,c){return Math.floor(Math.random()*(c-a+1))+a};return q}); -------------------------------------------------------------------------------- /libs/css.min.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-overflow-scrolling: touch; 3 | -webkit-tap-highlight-color: transparent; 4 | -webkit-touch-callout: none; 5 | outline: none; 6 | } 7 | body { 8 | background-color: #000; 9 | margin: 0; 10 | letter-spacing: 1px; 11 | font-family: "ATC Overlook", arial, sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -ms-touch-action: manipulation; 15 | touch-action: manipulation; 16 | -webkit-text-size-adjust: 100%; 17 | overflow: hidden; 18 | } 19 | ::-webkit-scrollbar { 20 | width: 6px; 21 | height: 6px; 22 | background-color: transparent; 23 | } 24 | ::-webkit-scrollbar-thumb { 25 | background-color: #333; 26 | border-radius: 6px; 27 | } 28 | canvas { 29 | display: block; 30 | width: 100%; 31 | height: 100%; 32 | } 33 | a { 34 | color: #87d0f0; 35 | text-decoration: none; 36 | transition: all 200ms; 37 | } 38 | a:hover { 39 | color: #a0dbf2; 40 | } 41 | img { 42 | outline: none; 43 | border: none; 44 | } 45 | .canvas-box { 46 | position: absolute; 47 | top: 0; 48 | left: 0; 49 | width: 100%; 50 | height: 100%; 51 | z-index: 1; 52 | } 53 | .title2d { 54 | position: relative; 55 | width: 150px; 56 | height: 40px; 57 | text-align: center; 58 | opacity: 0; 59 | transform: scale(0, 0); 60 | transition: all 300ms; 61 | } 62 | .title2d.show { 63 | opacity: 1; 64 | transform: scale(1, 1); 65 | } 66 | .title2d.show .t { 67 | opacity: 1; 68 | transform: translateY(0); 69 | } 70 | .title2d .t { 71 | position: absolute; 72 | top: 0; 73 | left: 0; 74 | width: 100%; 75 | height: 100%; 76 | line-height: 40px; 77 | font-size: 16px; 78 | color: #ffffff; 79 | z-index: 2; 80 | opacity: 0; 81 | transform: translateY(10px); 82 | transition: all 300ms; 83 | } 84 | .title2d .b { 85 | position: absolute; 86 | top: 0; 87 | left: 0; 88 | width: 100%; 89 | height: 100%; 90 | background-color: rgba(0, 0, 0, 0.3); 91 | } 92 | .title2d .l1 { 93 | width: 10px; 94 | height: 10px; 95 | position: absolute; 96 | top: 0; 97 | left: 0; 98 | border-top: solid 1px #00ff00; 99 | border-left: solid 1px #00ff00; 100 | } 101 | .title2d .l2 { 102 | width: 10px; 103 | height: 10px; 104 | position: absolute; 105 | bottom: 0; 106 | right: 0; 107 | border-bottom: solid 1px #00ff00; 108 | border-right: solid 1px #00ff00; 109 | } 110 | .label2 { 111 | width: 300px; 112 | color: #ffffff; 113 | font-size: 12px; 114 | opacity: 0; 115 | transition: all 300ms; 116 | } 117 | .label2.show { 118 | opacity: 1; 119 | } 120 | .label2.show p { 121 | transform: scale(1, 1); 122 | } 123 | .label2 p { 124 | transition: 300ms; 125 | transform: scale(0, 0); 126 | } 127 | .label2 i { 128 | color: #00ff00; 129 | } 130 | .footer { 131 | position: fixed; 132 | height: 25px; 133 | width: 100%; 134 | left: 0; 135 | bottom: 0; 136 | background-color: rgba(0, 0, 0, 0.8); 137 | z-index: 20; 138 | display: flex; 139 | align-items: center; 140 | justify-content: space-between; 141 | } 142 | .footer .copyright { 143 | right: 10px; 144 | font-size: 12px; 145 | color: #666666; 146 | margin-right: 10px; 147 | } 148 | .footer .copyright a { 149 | color: #666666; 150 | } 151 | .footer .copyright a:hover { 152 | color: #87d0f0; 153 | } 154 | @keyframes AnimePlay { 155 | 0% { 156 | transform: scale(1, 1); 157 | } 158 | 100% { 159 | transform: scale(1, 4); 160 | } 161 | } 162 | @-webkit-keyframes AnimePlay { 163 | 0% { 164 | transform: scale(1, 1); 165 | } 166 | 100% { 167 | transform: scale(1, 4); 168 | } 169 | } 170 | .animePlay { 171 | -webkit-animation-name: AnimePlay; 172 | -webkit-animation-duration: 600ms; 173 | -webkit-animation-iteration-count: infinite; 174 | animation-name: AnimePlay; 175 | animation-duration: 600ms; 176 | animation-iteration-count: infinite; 177 | } 178 | .footer .music { 179 | margin-left: 10px; 180 | display: flex; 181 | align-items: center; 182 | height: 100%; 183 | } 184 | .footer .music .play-btn { 185 | display: flex; 186 | align-items: center; 187 | height: 100%; 188 | padding: 0 10px; 189 | cursor: pointer; 190 | transition: all 200ms; 191 | } 192 | .footer .music .play-btn i { 193 | display: block; 194 | width: 1px; 195 | height: 2px; 196 | background-color: #ccc; 197 | margin-right: 4px; 198 | transition: all 200ms; 199 | } 200 | .footer .music .play-btn i:nth-child(2) { 201 | animation-delay: 50ms; 202 | -webkit-animation-delay: 100ms; 203 | } 204 | .footer .music .play-btn i:nth-child(3) { 205 | animation-delay: 100ms; 206 | -webkit-animation-delay: 200ms; 207 | } 208 | .footer .music .play-btn i:nth-child(4) { 209 | animation-delay: 150ms; 210 | -webkit-animation-delay: 300ms; 211 | } 212 | .footer .music .play-btn i:nth-child(5) { 213 | animation-delay: 200ms; 214 | -webkit-animation-delay: 400ms; 215 | } 216 | .footer .music .play-btn:hover i { 217 | background-color: #00ff00; 218 | } 219 | .footer .music .next-btn { 220 | padding: 0 10px; 221 | height: 100%; 222 | display: flex; 223 | align-items: center; 224 | cursor: pointer; 225 | transition: all 200ms; 226 | } 227 | .footer .music .next-btn:hover div { 228 | border-left: solid 8px #00ff00; 229 | } 230 | .footer .music .next-btn > div { 231 | box-shadow: 0 0 2px #000000; 232 | box-sizing: border-box; 233 | border: solid 4px transparent; 234 | border-left: solid 8px #fff; 235 | } 236 | .footer .music .next-btn > div:nth-child(2) { 237 | margin-left: -8px; 238 | } 239 | .footer .music .music-name { 240 | font-size: 12px; 241 | color: #888; 242 | } 243 | .mask { 244 | background-color: #222; 245 | position: fixed; 246 | top: 0; 247 | left: 0; 248 | width: 100%; 249 | height: 100%; 250 | z-index: 99; 251 | } 252 | .ship-info-box { 253 | position: fixed; 254 | top: 60%; 255 | left: 50%; 256 | transform: translateX(-50%); 257 | text-align: center; 258 | z-index: 100; 259 | padding: 20px; 260 | color: #ccc; 261 | font-size: 12px; 262 | } 263 | .ship-info-box .ship-type { 264 | height: 20px; 265 | line-height: 20px; 266 | overflow: hidden; 267 | } 268 | .ship-info-box .ship-type ul { 269 | display: block; 270 | margin: 0; 271 | padding: 0; 272 | transition: all 300ms; 273 | } 274 | .ship-info-box .ship-type ul > li { 275 | display: block; 276 | height: 20px; 277 | line-height: 20px; 278 | } 279 | .ship-info-box .speed { 280 | font-size: 12px; 281 | color: #00ff00; 282 | } 283 | .ship-info-box .speed span { 284 | color: #fff; 285 | } 286 | .ship-info-box .speed i { 287 | font-style: normal; 288 | } 289 | .ship-info-box .btn { 290 | position: relative; 291 | width: 150px; 292 | height: 30px; 293 | line-height: 30px; 294 | text-align: center; 295 | margin-top: 10px; 296 | } 297 | .ship-info-box .btn .btn-back { 298 | transition: all 300ms; 299 | position: absolute; 300 | top: 0; 301 | left: 0; 302 | width: 100%; 303 | height: 100%; 304 | background-color: rgba(0, 0, 0, 0.3); 305 | transform: scale(1, 0); 306 | } 307 | .ship-info-box .btn .btn-word { 308 | transition: all 600ms; 309 | position: absolute; 310 | top: 0; 311 | left: 0; 312 | width: 100%; 313 | height: 100%; 314 | transform: translateX(15px); 315 | color: #ccc; 316 | z-index: 2; 317 | text-align: center; 318 | opacity: 0; 319 | } 320 | .ship-info-box .btn .line { 321 | z-index: 2; 322 | transition: all 300ms; 323 | width: 10px; 324 | height: 10px; 325 | position: absolute; 326 | transform: scale(0, 0); 327 | opacity: 0; 328 | } 329 | .ship-info-box .btn .line1 { 330 | top: 0; 331 | left: 0; 332 | border-top: solid 1px #00ff00; 333 | border-left: solid 1px #00ff00; 334 | } 335 | .ship-info-box .btn .line2 { 336 | bottom: 0; 337 | right: 0; 338 | border-bottom: solid 1px #00ff00; 339 | border-right: solid 1px #00ff00; 340 | } 341 | .ship-info-box .btn.show { 342 | cursor: pointer; 343 | } 344 | .ship-info-box .btn.show .btn-back { 345 | transform: scale(1, 1); 346 | } 347 | .ship-info-box .btn.show .btn-word { 348 | transform: translateX(0px); 349 | opacity: 1; 350 | } 351 | .ship-info-box .btn.show .line { 352 | transform: scale(1, 1); 353 | opacity: 1; 354 | } 355 | .ship-info-box .btn:hover .line { 356 | width: 30px; 357 | height: 15px; 358 | } 359 | .ship-info-box .remind { 360 | font-size: 12px; 361 | color: #00ff00; 362 | opacity: 0; 363 | transition: all 300ms; 364 | transform: translateY(15px); 365 | } 366 | .ship-info-box .remind.show { 367 | opacity: 1; 368 | transform: translateY(0); 369 | } 370 | .logo { 371 | position: fixed; 372 | opacity: 0.7; 373 | top: 50vh; 374 | left: 50vw; 375 | width: 120px; 376 | height: auto; 377 | transform: translate(-50%, -50%); 378 | z-index: 100; 379 | transition: all 1000ms; 380 | } 381 | .logo.show { 382 | top: 1vw; 383 | left: 1vw; 384 | width: 50px; 385 | transform: translate(0, 0); 386 | } 387 | .title-box { 388 | position: absolute; 389 | top: 40%; 390 | left: 8%; 391 | transform: translateY(-50%); 392 | color: #fff; 393 | z-index: 2; 394 | pointer-events: none; 395 | } 396 | .title-box .title { 397 | font-size: 36px; 398 | perspective: 600px; 399 | -webkit-perspective: 600px; 400 | -moz-perspective: 600px; 401 | display: flex; 402 | 403 | } 404 | .title-box .title span { 405 | display: inline-block; 406 | transform: rotateY(90deg); 407 | transition: all 5000ms; 408 | transform-style: preserve-3d; 409 | -webkit-transform-style: preserve-3d; 410 | } 411 | .title-box.show .title span { 412 | transform: rotateY(0.1deg); 413 | } 414 | .title-box .title span i{ 415 | display: inline-block; 416 | transition: opacity 5000ms; 417 | font-style: normal; 418 | opacity: 0; 419 | } 420 | .title-box.show .title span i{ 421 | opacity: 1; 422 | } 423 | 424 | .title-box.show .say { 425 | opacity: 1; 426 | transform: translateX(0); 427 | } 428 | .title-box .say { 429 | font-size: 12px; 430 | color: #888; 431 | padding: 20px 0 20px 40px; 432 | opacity: 0; 433 | transform: translateX(20px); 434 | transition: all 2000ms; 435 | transition-delay: 2000ms; 436 | } 437 | .title-box.show .use { 438 | opacity: 1; 439 | transform: translateY(0); 440 | } 441 | .title-box .say p { 442 | padding: 0; 443 | } 444 | .title-box .use { 445 | font-size: 14px; 446 | color: #fff; 447 | display: flex; 448 | align-items: center; 449 | padding-left: 40px; 450 | opacity: 0; 451 | transform: translateY(20px); 452 | transition: all 2000ms; 453 | transition-delay: 2000ms; 454 | } 455 | .title-box .use div { 456 | height: 1px; 457 | background-color: #fff; 458 | width: 80px; 459 | margin-right: 10px; 460 | } 461 | .menu { 462 | position: fixed; 463 | top: 50%; 464 | right: 20px; 465 | transform: translateY(-50%); 466 | z-index: 20; 467 | display: block; 468 | margin: 0; 469 | padding: 0; 470 | } 471 | .menu > li { 472 | position: relative; 473 | width: 20px; 474 | height: 30px; 475 | line-height: 20px; 476 | font-size: 12px; 477 | color: #fff; 478 | text-align: center; 479 | transform: translateX(50px) translateY(25px); 480 | padding: 0; 481 | display: block; 482 | margin-bottom: 10px; 483 | cursor: pointer; 484 | } 485 | .menu > li > div { 486 | position: relative; 487 | width: 20px; 488 | height: 20px; 489 | transform: rotate(45deg); 490 | transition: all 300ms; 491 | } 492 | .menu > li:first-child { 493 | transition: all 200ms; 494 | } 495 | .menu > li:nth-child(2) { 496 | transition: all 500ms; 497 | } 498 | .menu > li:nth-child(3) { 499 | transition: all 800ms; 500 | } 501 | .menu.show > li { 502 | transform: translateX(0) translateY(0); 503 | opacity: 1; 504 | } 505 | .menu li > div::before { 506 | content: ""; 507 | position: absolute; 508 | top: 0; 509 | left: 0; 510 | width: 19px; 511 | height: 19px; 512 | border-top: solid 1px #fff; 513 | border-left: solid 1px #fff; 514 | transition: all 300ms; 515 | } 516 | .menu li > div::after { 517 | content: ""; 518 | position: absolute; 519 | bottom: 0; 520 | right: 0; 521 | width: 19px; 522 | height: 19px; 523 | border-bottom: solid 1px #fff; 524 | border-right: solid 1px #fff; 525 | transition: all 300ms; 526 | } 527 | .menu > li > div > div { 528 | transform: rotate(-45deg); 529 | } 530 | .menu li:hover > div { 531 | transform: rotate(0) !important; 532 | } 533 | .menu li:hover > div::before { 534 | width: 5px; 535 | height: 5px; 536 | border-top: solid 1px #0f0; 537 | border-left: solid 1px #0f0; 538 | } 539 | .menu li:hover > div::after { 540 | width: 5px; 541 | height: 5px; 542 | border-bottom: solid 1px #0f0; 543 | border-right: solid 1px #0f0; 544 | } 545 | .menu li:hover > div > div { 546 | transform: rotate(0); 547 | } 548 | .pages-box { 549 | position: fixed; 550 | top: 0; 551 | right: 0; 552 | height: calc(100% - 25px); 553 | box-sizing: border-box; 554 | z-index: 18; 555 | background-color: rgba(0, 0, 0, 0.8); 556 | overflow: hidden; 557 | opacity: 0; 558 | width: 50vw; 559 | transform: translateX(45vw); 560 | transition: all 300ms; 561 | color: #fff; 562 | } 563 | @keyframes lineMove { 564 | 0% { 565 | transform: translateY(0); 566 | } 567 | 100% { 568 | transform: translateY(-300vh); 569 | } 570 | } 571 | @-webkit-keyframes lineMove { 572 | 0% { 573 | -webkit-transform: translateY(0); 574 | } 575 | 100% { 576 | -webkit-transform: translateY(-300vh); 577 | } 578 | } 579 | .pages-box::after { 580 | content: ""; 581 | position: absolute; 582 | bottom: -60vh; 583 | left: 0; 584 | height: 60vh; 585 | width: 1px; 586 | background: linear-gradient(transparent, rgb(30, 40, 150), transparent); 587 | transform: translateY(0); 588 | -webkit-transform: translateY(0); 589 | } 590 | .pages-box.show { 591 | transform: translateX(0); 592 | opacity: 1; 593 | } 594 | .pages-box.show::after { 595 | animation-name: lineMove; 596 | animation-duration: 5000ms; 597 | animation-iteration-count: infinite; 598 | } 599 | .pages-box > div { 600 | position: absolute; 601 | top: 0; 602 | left: 0; 603 | box-sizing: border-box; 604 | width: 100%; 605 | height: 100%; 606 | padding: 60px; 607 | overflow-x: hidden; 608 | overflow-y: auto; 609 | opacity: 0; 610 | transition: all 300ms; 611 | transform: translateX(25px); 612 | font-size: 12px; 613 | z-index: 18; 614 | } 615 | .pages-box > div.show { 616 | opacity: 1; 617 | transform: translateX(0); 618 | z-index: 20; 619 | } 620 | .pages-box .page-title-box .t { 621 | font-size: 24px; 622 | letter-spacing: 2px; 623 | color: #ccc; 624 | } 625 | .pages-box .page-title-box .t i { 626 | color: #fff; 627 | } 628 | .pages-box .page-title-box .i { 629 | font-style: italic; 630 | color: #aaa; 631 | font-size: 12px; 632 | margin-top: 10px; 633 | opacity: 0.8; 634 | } 635 | .pages-box .logo-img { 636 | height: 40px; 637 | width: auto; 638 | display: block; 639 | margin: 0 auto; 640 | padding: 100px 0 60px 0; 641 | } 642 | .pages-box .illustration { 643 | display: block; 644 | max-width: 80%; 645 | margin: 60px auto 60px auto; 646 | border-radius: 8px; 647 | border: solid 1px #888; 648 | } 649 | .pages-box .words-box { 650 | padding: 40px 0; 651 | } 652 | .pages-box .words-box .t { 653 | color: #fff; 654 | font-size: 14px; 655 | text-align: center; 656 | } 657 | .pages-box .words-box .tl { 658 | text-align: left; 659 | margin-top: 20px; 660 | } 661 | .pages-box .words-box .i { 662 | color: #ccc; 663 | margin-top: 20px; 664 | } 665 | .pages-box .words-box .vcode { 666 | width: 200px; 667 | height: auto; 668 | border-radius: 6px; 669 | display: block; 670 | margin: 0 auto; 671 | transform: scale(1, 1); 672 | transition: 200ms; 673 | } 674 | .pages-box .words-box .vcode:hover { 675 | transform: scale(2, 2); 676 | } 677 | .pages-box .words-box .z { 678 | display: flex; 679 | flex-wrap: wrap; 680 | width: 100%; 681 | padding: 0; 682 | margin: 20px 0 0 0; 683 | } 684 | .pages-box .words-box .z > li { 685 | position: relative; 686 | flex: none; 687 | min-width: 20%; 688 | font-size: 12px; 689 | color: #fff; 690 | box-sizing: border-box; 691 | padding: 5px; 692 | list-style: none; 693 | margin: 0; 694 | } 695 | .pages-box .words-box .z > li > div { 696 | display: flex; 697 | height: 16px; 698 | line-height: 16px; 699 | } 700 | .pages-box .words-box .z > li img { 701 | width: 14px; 702 | height: auto; 703 | } 704 | .pages-box .words-box .z > li:first-child { 705 | color: #e8c98b; 706 | } 707 | .pages-box .words-box .z > li:nth-child(2) { 708 | color: #f5dba2; 709 | } 710 | .pages-box .words-box .z > li:nth-child(3) { 711 | color: #fae5bc; 712 | } 713 | .pages-box .words-box .z > li > .s { 714 | color: #888; 715 | font-style: italic; 716 | } 717 | .pages-box .words-box .z > li > .w { 718 | color: #888; 719 | } 720 | .pages-box .foot-info { 721 | color: #aaa; 722 | font-style: italic; 723 | text-align: center; 724 | margin-top: 100px; 725 | } 726 | .pages-box .t-box { 727 | padding: 40px 0; 728 | text-align: center; 729 | } 730 | .pages-box .t-box img { 731 | width: 200px; 732 | height: auto; 733 | border-radius: 6px; 734 | border: solid 1px #888; 735 | margin-bottom: 10px; 736 | } 737 | .pages-box .t-box .bookall { 738 | width: 100px; 739 | height: auto; 740 | border: none; 741 | } 742 | .pages-box .t-box { 743 | font-size: 12px; 744 | color: #aaa; 745 | } 746 | .pages-box .card { 747 | display: flex; 748 | align-items: center; 749 | } 750 | .pages-box .card img { 751 | flex: none; 752 | max-width: 80px; 753 | border-radius: 100px; 754 | border: solid 3px rgba(255, 255, 255, 0.8); 755 | margin-right: 20px; 756 | } 757 | .pages-box .card > div { 758 | flex: 1; 759 | color: #888; 760 | font-size: 12px; 761 | } 762 | .pages-box .card > div > div span { 763 | color: #fff; 764 | } 765 | .pages-box .card > div > div:nth-child(2) { 766 | margin-top: 10px; 767 | } 768 | #page-m { 769 | background-image: url("./imgs/page2back.png"); 770 | background-repeat: no-repeat; 771 | background-size: 100% auto; 772 | background-position: center bottom; 773 | } 774 | .close { 775 | position: fixed; 776 | width: 20px; 777 | height: 20px; 778 | border: solid 1px #fff; 779 | background-color: #f00; 780 | bottom: 35px; 781 | left: 50%; 782 | margin-left: -10px; 783 | cursor: pointer; 784 | transform: translateY(50px) rotate(45deg); 785 | z-index: 22; 786 | opacity: 0; 787 | transition: all 300ms; 788 | } 789 | .close.show { 790 | transform: rotate(45deg) translateY(0); 791 | opacity: 1; 792 | } 793 | .close:hover { 794 | transform: rotate(135deg) translateY(0); 795 | } 796 | .close div { 797 | position: absolute; 798 | background-color: #fff; 799 | } 800 | .close div:first-child { 801 | top: 10px; 802 | left: 5px; 803 | width: 10px; 804 | height: 1px; 805 | } 806 | .close div:nth-child(2) { 807 | top: 5px; 808 | left: 10px; 809 | width: 1px; 810 | height: 10px; 811 | } 812 | .pointernone { 813 | pointer-events: none; 814 | user-select: none; 815 | -webkit-user-select: none; 816 | } 817 | @media all and (max-width: 768px) { 818 | .logo { 819 | top: 25vh; 820 | left: 50vw; 821 | width: 80px; 822 | height: auto; 823 | transform: translate(-50%, -50%); 824 | transition: all 800ms; 825 | } 826 | .logo.show { 827 | top: 1vw; 828 | left: 1vw; 829 | width: 50px; 830 | transform: translate(0, 0); 831 | } 832 | .title-box { 833 | top: 10%; 834 | left: 0; 835 | width: 100%; 836 | box-sizing: border-box; 837 | padding: 0 10px; 838 | text-align: center; 839 | transform: translateY(0); 840 | display: flex; 841 | flex-direction: column; 842 | align-items: center; 843 | } 844 | .title-box .title { 845 | font-size: 20px; 846 | } 847 | .title-box .say { 848 | text-align: left; 849 | padding: 10px 0; 850 | } 851 | .title-box .say p { 852 | margin: 0; 853 | } 854 | .copyright { 855 | display: none; 856 | } 857 | .pages-box { 858 | width: 100vw; 859 | transform: translateX(100vw); 860 | } 861 | .pages-box .words-box .vcode:hover { 862 | transform: scale(1.1, 1.1); 863 | } 864 | } 865 | -------------------------------------------------------------------------------- /libs/deri.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 求导 3 | * 为了算x轴所对应的y值 4 | * **/ 5 | const Deri = { 6 | /** 7 | * @param ps 包含了4个数的数组 8 | * @param targ 目标值 9 | * @param t 10 | * @constructor 11 | */ 12 | NTBezierFunc: function (ps, targ, t) { 13 | return (1.0 - t) * (1.0 - t) * (1.0 - t) * ps[0] + 3 * (1.0 - t) * (1.0 - t) * t * ps[1] + 3 * (1.0 - t) * t * t * ps[2] + t * t * t * ps[3] - targ; 14 | }, 15 | /** 16 | * 求导数公式 17 | * @param ps 18 | * @param targ 19 | * @param t 20 | * @constructor 21 | */ 22 | DeltaNTBezierFunc: function (ps, targ, t) { 23 | const dt = 1e-8; 24 | return (this.NTBezierFunc(ps, targ, t) - this.NTBezierFunc(ps, targ, t - dt)) / dt; 25 | }, 26 | /** 27 | * 开始创建曲线 28 | **/ 29 | start: function (x) { 30 | const dot_x = [0, 0, 28, 42]; // 0 0 25 42 / 0, 0, 28, 42 31 | const dot_y = [0, 14.5, 0, 0]; // -0.4, 15, 0.1, 0 / 0, 15, 0, 0 32 | let t = 0.8; // t的初始值 33 | for (let i = 0; i < 512; i++) { 34 | t = t - this.NTBezierFunc(dot_x, x, t) / this.DeltaNTBezierFunc(dot_x, x, t); 35 | if (this.NTBezierFunc(dot_x, x, t) <= 0.00001) { 36 | break; 37 | } 38 | } 39 | const res_y = this.NTBezierFunc(dot_y, 0, t); 40 | return res_y; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /libs/fastclick.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict'; 3 | 4 | /** 5 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. 6 | * 7 | * @codingstandard ftlabs-jsv2 8 | * @copyright The Financial Times Limited [All Rights Reserved] 9 | * @license MIT License (see LICENSE.txt) 10 | */ 11 | 12 | /*jslint browser:true, node:true*/ 13 | /*global define, Event, Node*/ 14 | 15 | 16 | /** 17 | * Instantiate fast-clicking listeners on the specified layer. 18 | * 19 | * @constructor 20 | * @param {Element} layer The layer to listen on 21 | * @param {Object} [options={}] The options to override the defaults 22 | */ 23 | function FastClick(layer, options) { 24 | var oldOnClick; 25 | 26 | options = options || {}; 27 | 28 | /** 29 | * Whether a click is currently being tracked. 30 | * 31 | * @type boolean 32 | */ 33 | this.trackingClick = false; 34 | 35 | 36 | /** 37 | * Timestamp for when click tracking started. 38 | * 39 | * @type number 40 | */ 41 | this.trackingClickStart = 0; 42 | 43 | 44 | /** 45 | * The element being tracked for a click. 46 | * 47 | * @type EventTarget 48 | */ 49 | this.targetElement = null; 50 | 51 | 52 | /** 53 | * X-coordinate of touch start event. 54 | * 55 | * @type number 56 | */ 57 | this.touchStartX = 0; 58 | 59 | 60 | /** 61 | * Y-coordinate of touch start event. 62 | * 63 | * @type number 64 | */ 65 | this.touchStartY = 0; 66 | 67 | 68 | /** 69 | * ID of the last touch, retrieved from Touch.identifier. 70 | * 71 | * @type number 72 | */ 73 | this.lastTouchIdentifier = 0; 74 | 75 | 76 | /** 77 | * Touchmove boundary, beyond which a click will be cancelled. 78 | * 79 | * @type number 80 | */ 81 | this.touchBoundary = options.touchBoundary || 10; 82 | 83 | 84 | /** 85 | * The FastClick layer. 86 | * 87 | * @type Element 88 | */ 89 | this.layer = layer; 90 | 91 | /** 92 | * The minimum time between tap(touchstart and touchend) events 93 | * 94 | * @type number 95 | */ 96 | this.tapDelay = options.tapDelay || 200; 97 | 98 | /** 99 | * The maximum time for a tap 100 | * 101 | * @type number 102 | */ 103 | this.tapTimeout = options.tapTimeout || 700; 104 | 105 | if (FastClick.notNeeded(layer)) { 106 | return; 107 | } 108 | 109 | // Some old versions of Android don't have Function.prototype.bind 110 | function bind(method, context) { 111 | return function() { return method.apply(context, arguments); }; 112 | } 113 | 114 | 115 | var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel']; 116 | var context = this; 117 | for (var i = 0, l = methods.length; i < l; i++) { 118 | context[methods[i]] = bind(context[methods[i]], context); 119 | } 120 | 121 | // Set up event handlers as required 122 | if (deviceIsAndroid) { 123 | layer.addEventListener('mouseover', this.onMouse, true); 124 | layer.addEventListener('mousedown', this.onMouse, true); 125 | layer.addEventListener('mouseup', this.onMouse, true); 126 | } 127 | 128 | layer.addEventListener('click', this.onClick, true); 129 | layer.addEventListener('touchstart', this.onTouchStart, false); 130 | layer.addEventListener('touchmove', this.onTouchMove, false); 131 | layer.addEventListener('touchend', this.onTouchEnd, false); 132 | layer.addEventListener('touchcancel', this.onTouchCancel, false); 133 | 134 | // Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) 135 | // which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick 136 | // layer when they are cancelled. 137 | if (!Event.prototype.stopImmediatePropagation) { 138 | layer.removeEventListener = function(type, callback, capture) { 139 | var rmv = Node.prototype.removeEventListener; 140 | if (type === 'click') { 141 | rmv.call(layer, type, callback.hijacked || callback, capture); 142 | } else { 143 | rmv.call(layer, type, callback, capture); 144 | } 145 | }; 146 | 147 | layer.addEventListener = function(type, callback, capture) { 148 | var adv = Node.prototype.addEventListener; 149 | if (type === 'click') { 150 | adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { 151 | if (!event.propagationStopped) { 152 | callback(event); 153 | } 154 | }), capture); 155 | } else { 156 | adv.call(layer, type, callback, capture); 157 | } 158 | }; 159 | } 160 | 161 | // If a handler is already declared in the element's onclick attribute, it will be fired before 162 | // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and 163 | // adding it as listener. 164 | if (typeof layer.onclick === 'function') { 165 | 166 | // Android browser on at least 3.2 requires a new reference to the function in layer.onclick 167 | // - the old one won't work if passed to addEventListener directly. 168 | oldOnClick = layer.onclick; 169 | layer.addEventListener('click', function(event) { 170 | oldOnClick(event); 171 | }, false); 172 | layer.onclick = null; 173 | } 174 | } 175 | 176 | /** 177 | * Windows Phone 8.1 fakes user agent string to look like Android and iPhone. 178 | * 179 | * @type boolean 180 | */ 181 | var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0; 182 | 183 | /** 184 | * Android requires exceptions. 185 | * 186 | * @type boolean 187 | */ 188 | var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone; 189 | 190 | 191 | /** 192 | * iOS requires exceptions. 193 | * 194 | * @type boolean 195 | */ 196 | var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone; 197 | 198 | 199 | /** 200 | * iOS 4 requires an exception for select elements. 201 | * 202 | * @type boolean 203 | */ 204 | var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent); 205 | 206 | 207 | /** 208 | * iOS 6.0-7.* requires the target element to be manually derived 209 | * 210 | * @type boolean 211 | */ 212 | var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent); 213 | 214 | /** 215 | * BlackBerry requires exceptions. 216 | * 217 | * @type boolean 218 | */ 219 | var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0; 220 | 221 | /** 222 | * Determine whether a given element requires a native click. 223 | * 224 | * @param {EventTarget|Element} target Target DOM element 225 | * @returns {boolean} Returns true if the element needs a native click 226 | */ 227 | FastClick.prototype.needsClick = function(target) { 228 | switch (target.nodeName.toLowerCase()) { 229 | 230 | // Don't send a synthetic click to disabled inputs (issue #62) 231 | case 'button': 232 | case 'select': 233 | case 'textarea': 234 | if (target.disabled) { 235 | return true; 236 | } 237 | 238 | break; 239 | case 'input': 240 | 241 | // File inputs need real clicks on iOS 6 due to a browser bug (issue #68) 242 | if ((deviceIsIOS && target.type === 'file') || target.disabled) { 243 | return true; 244 | } 245 | 246 | break; 247 | case 'label': 248 | case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames 249 | case 'video': 250 | return true; 251 | } 252 | 253 | return (/\bneedsclick\b/).test(target.className); 254 | }; 255 | 256 | 257 | /** 258 | * Determine whether a given element requires a call to focus to simulate click into element. 259 | * 260 | * @param {EventTarget|Element} target Target DOM element 261 | * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. 262 | */ 263 | FastClick.prototype.needsFocus = function(target) { 264 | switch (target.nodeName.toLowerCase()) { 265 | case 'textarea': 266 | return true; 267 | case 'select': 268 | return !deviceIsAndroid; 269 | case 'input': 270 | switch (target.type) { 271 | case 'button': 272 | case 'checkbox': 273 | case 'file': 274 | case 'image': 275 | case 'radio': 276 | case 'submit': 277 | return false; 278 | } 279 | 280 | // No point in attempting to focus disabled inputs 281 | return !target.disabled && !target.readOnly; 282 | default: 283 | return (/\bneedsfocus\b/).test(target.className); 284 | } 285 | }; 286 | 287 | 288 | /** 289 | * Send a click event to the specified element. 290 | * 291 | * @param {EventTarget|Element} targetElement 292 | * @param {Event} event 293 | */ 294 | FastClick.prototype.sendClick = function(targetElement, event) { 295 | var clickEvent, touch; 296 | 297 | // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24) 298 | if (document.activeElement && document.activeElement !== targetElement) { 299 | document.activeElement.blur(); 300 | } 301 | 302 | touch = event.changedTouches[0]; 303 | 304 | // Synthesise a click event, with an extra attribute so it can be tracked 305 | clickEvent = document.createEvent('MouseEvents'); 306 | clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 307 | clickEvent.forwardedTouchEvent = true; 308 | targetElement.dispatchEvent(clickEvent); 309 | }; 310 | 311 | FastClick.prototype.determineEventType = function(targetElement) { 312 | 313 | //Issue #159: Android Chrome Select Box does not open with a synthetic click event 314 | if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') { 315 | return 'mousedown'; 316 | } 317 | 318 | return 'click'; 319 | }; 320 | 321 | 322 | /** 323 | * @param {EventTarget|Element} targetElement 324 | */ 325 | FastClick.prototype.focus = function(targetElement) { 326 | var length; 327 | 328 | // Issue #160: on iOS 7, some input elements (e.g. date datetime month) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724. 329 | if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month' && targetElement.type !== 'email') { 330 | length = targetElement.value.length; 331 | targetElement.setSelectionRange(length, length); 332 | } else { 333 | targetElement.focus(); 334 | } 335 | }; 336 | 337 | 338 | /** 339 | * Check whether the given target element is a child of a scrollable layer and if so, set a flag on it. 340 | * 341 | * @param {EventTarget|Element} targetElement 342 | */ 343 | FastClick.prototype.updateScrollParent = function(targetElement) { 344 | var scrollParent, parentElement; 345 | 346 | scrollParent = targetElement.fastClickScrollParent; 347 | 348 | // Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the 349 | // target element was moved to another parent. 350 | if (!scrollParent || !scrollParent.contains(targetElement)) { 351 | parentElement = targetElement; 352 | do { 353 | if (parentElement.scrollHeight > parentElement.offsetHeight) { 354 | scrollParent = parentElement; 355 | targetElement.fastClickScrollParent = parentElement; 356 | break; 357 | } 358 | 359 | parentElement = parentElement.parentElement; 360 | } while (parentElement); 361 | } 362 | 363 | // Always update the scroll top tracker if possible. 364 | if (scrollParent) { 365 | scrollParent.fastClickLastScrollTop = scrollParent.scrollTop; 366 | } 367 | }; 368 | 369 | 370 | /** 371 | * @param {EventTarget} targetElement 372 | * @returns {Element|EventTarget} 373 | */ 374 | FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { 375 | 376 | // On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node. 377 | if (eventTarget.nodeType === Node.TEXT_NODE) { 378 | return eventTarget.parentNode; 379 | } 380 | 381 | return eventTarget; 382 | }; 383 | 384 | 385 | /** 386 | * On touch start, record the position and scroll offset. 387 | * 388 | * @param {Event} event 389 | * @returns {boolean} 390 | */ 391 | FastClick.prototype.onTouchStart = function(event) { 392 | var targetElement, touch, selection; 393 | 394 | // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111). 395 | if (event.targetTouches.length > 1) { 396 | return true; 397 | } 398 | 399 | targetElement = this.getTargetElementFromEventTarget(event.target); 400 | touch = event.targetTouches[0]; 401 | 402 | if (deviceIsIOS) { 403 | 404 | // Only trusted events will deselect text on iOS (issue #49) 405 | selection = window.getSelection(); 406 | if (selection.rangeCount && !selection.isCollapsed) { 407 | return true; 408 | } 409 | 410 | if (!deviceIsIOS4) { 411 | 412 | // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23): 413 | // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched 414 | // with the same identifier as the touch event that previously triggered the click that triggered the alert. 415 | // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an 416 | // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform. 417 | // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string, 418 | // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long, 419 | // random integers, it's safe to to continue if the identifier is 0 here. 420 | if (touch.identifier && touch.identifier === this.lastTouchIdentifier) { 421 | event.preventDefault(); 422 | return false; 423 | } 424 | 425 | this.lastTouchIdentifier = touch.identifier; 426 | 427 | // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and: 428 | // 1) the user does a fling scroll on the scrollable layer 429 | // 2) the user stops the fling scroll with another tap 430 | // then the event.target of the last 'touchend' event will be the element that was under the user's finger 431 | // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check 432 | // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42). 433 | this.updateScrollParent(targetElement); 434 | } 435 | } 436 | 437 | this.trackingClick = true; 438 | this.trackingClickStart = event.timeStamp; 439 | this.targetElement = targetElement; 440 | 441 | this.touchStartX = touch.pageX; 442 | this.touchStartY = touch.pageY; 443 | 444 | // Prevent phantom clicks on fast double-tap (issue #36) 445 | if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { 446 | event.preventDefault(); 447 | } 448 | 449 | return true; 450 | }; 451 | 452 | 453 | /** 454 | * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. 455 | * 456 | * @param {Event} event 457 | * @returns {boolean} 458 | */ 459 | FastClick.prototype.touchHasMoved = function(event) { 460 | var touch = event.changedTouches[0], boundary = this.touchBoundary; 461 | 462 | if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { 463 | return true; 464 | } 465 | 466 | return false; 467 | }; 468 | 469 | 470 | /** 471 | * Update the last position. 472 | * 473 | * @param {Event} event 474 | * @returns {boolean} 475 | */ 476 | FastClick.prototype.onTouchMove = function(event) { 477 | if (!this.trackingClick) { 478 | return true; 479 | } 480 | 481 | // If the touch has moved, cancel the click tracking 482 | if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { 483 | this.trackingClick = false; 484 | this.targetElement = null; 485 | } 486 | 487 | return true; 488 | }; 489 | 490 | 491 | /** 492 | * Attempt to find the labelled control for the given label element. 493 | * 494 | * @param {EventTarget|HTMLLabelElement} labelElement 495 | * @returns {Element|null} 496 | */ 497 | FastClick.prototype.findControl = function(labelElement) { 498 | 499 | // Fast path for newer browsers supporting the HTML5 control attribute 500 | if (labelElement.control !== undefined) { 501 | return labelElement.control; 502 | } 503 | 504 | // All browsers under test that support touch events also support the HTML5 htmlFor attribute 505 | if (labelElement.htmlFor) { 506 | return document.getElementById(labelElement.htmlFor); 507 | } 508 | 509 | // If no for attribute exists, attempt to retrieve the first labellable descendant element 510 | // the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label 511 | return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea'); 512 | }; 513 | 514 | 515 | /** 516 | * On touch end, determine whether to send a click event at once. 517 | * 518 | * @param {Event} event 519 | * @returns {boolean} 520 | */ 521 | FastClick.prototype.onTouchEnd = function(event) { 522 | var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement; 523 | 524 | if (!this.trackingClick) { 525 | return true; 526 | } 527 | 528 | // Prevent phantom clicks on fast double-tap (issue #36) 529 | if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { 530 | this.cancelNextClick = true; 531 | return true; 532 | } 533 | 534 | if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) { 535 | return true; 536 | } 537 | 538 | // Reset to prevent wrong click cancel on input (issue #156). 539 | this.cancelNextClick = false; 540 | 541 | this.lastClickTime = event.timeStamp; 542 | 543 | trackingClickStart = this.trackingClickStart; 544 | this.trackingClick = false; 545 | this.trackingClickStart = 0; 546 | 547 | // On some iOS devices, the targetElement supplied with the event is invalid if the layer 548 | // is performing a transition or scroll, and has to be re-detected manually. Note that 549 | // for this to function correctly, it must be called *after* the event target is checked! 550 | // See issue #57; also filed as rdar://13048589 . 551 | if (deviceIsIOSWithBadTarget) { 552 | touch = event.changedTouches[0]; 553 | 554 | // In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null 555 | targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement; 556 | targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent; 557 | } 558 | 559 | targetTagName = targetElement.tagName.toLowerCase(); 560 | if (targetTagName === 'label') { 561 | forElement = this.findControl(targetElement); 562 | if (forElement) { 563 | this.focus(targetElement); 564 | if (deviceIsAndroid) { 565 | return false; 566 | } 567 | 568 | targetElement = forElement; 569 | } 570 | } else if (this.needsFocus(targetElement)) { 571 | 572 | // Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through. 573 | // Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37). 574 | if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) { 575 | this.targetElement = null; 576 | return false; 577 | } 578 | 579 | this.focus(targetElement); 580 | this.sendClick(targetElement, event); 581 | 582 | // Select elements need the event to go through on iOS 4, otherwise the selector menu won't open. 583 | // Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others) 584 | if (!deviceIsIOS || targetTagName !== 'select') { 585 | this.targetElement = null; 586 | event.preventDefault(); 587 | } 588 | 589 | return false; 590 | } 591 | 592 | if (deviceIsIOS && !deviceIsIOS4) { 593 | 594 | // Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled 595 | // and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42). 596 | scrollParent = targetElement.fastClickScrollParent; 597 | if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) { 598 | return true; 599 | } 600 | } 601 | 602 | // Prevent the actual click from going though - unless the target node is marked as requiring 603 | // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted. 604 | if (!this.needsClick(targetElement)) { 605 | event.preventDefault(); 606 | this.sendClick(targetElement, event); 607 | } 608 | 609 | return false; 610 | }; 611 | 612 | 613 | /** 614 | * On touch cancel, stop tracking the click. 615 | * 616 | * @returns {void} 617 | */ 618 | FastClick.prototype.onTouchCancel = function() { 619 | this.trackingClick = false; 620 | this.targetElement = null; 621 | }; 622 | 623 | 624 | /** 625 | * Determine mouse events which should be permitted. 626 | * 627 | * @param {Event} event 628 | * @returns {boolean} 629 | */ 630 | FastClick.prototype.onMouse = function(event) { 631 | 632 | // If a target element was never set (because a touch event was never fired) allow the event 633 | if (!this.targetElement) { 634 | return true; 635 | } 636 | 637 | if (event.forwardedTouchEvent) { 638 | return true; 639 | } 640 | 641 | // Programmatically generated events targeting a specific element should be permitted 642 | if (!event.cancelable) { 643 | return true; 644 | } 645 | 646 | // Derive and check the target element to see whether the mouse event needs to be permitted; 647 | // unless explicitly enabled, prevent non-touch click events from triggering actions, 648 | // to prevent ghost/doubleclicks. 649 | if (!this.needsClick(this.targetElement) || this.cancelNextClick) { 650 | 651 | // Prevent any user-added listeners declared on FastClick element from being fired. 652 | if (event.stopImmediatePropagation) { 653 | event.stopImmediatePropagation(); 654 | } else { 655 | 656 | // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) 657 | event.propagationStopped = true; 658 | } 659 | 660 | // Cancel the event 661 | event.stopPropagation(); 662 | event.preventDefault(); 663 | 664 | return false; 665 | } 666 | 667 | // If the mouse event is permitted, return true for the action to go through. 668 | return true; 669 | }; 670 | 671 | 672 | /** 673 | * On actual clicks, determine whether this is a touch-generated click, a click action occurring 674 | * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or 675 | * an actual click which should be permitted. 676 | * 677 | * @param {Event} event 678 | * @returns {boolean} 679 | */ 680 | FastClick.prototype.onClick = function(event) { 681 | var permitted; 682 | 683 | // It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early. 684 | if (this.trackingClick) { 685 | this.targetElement = null; 686 | this.trackingClick = false; 687 | return true; 688 | } 689 | 690 | // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target. 691 | if (event.target.type === 'submit' && event.detail === 0) { 692 | return true; 693 | } 694 | 695 | permitted = this.onMouse(event); 696 | 697 | // Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through. 698 | if (!permitted) { 699 | this.targetElement = null; 700 | } 701 | 702 | // If clicks are permitted, return true for the action to go through. 703 | return permitted; 704 | }; 705 | 706 | 707 | /** 708 | * Remove all FastClick's event listeners. 709 | * 710 | * @returns {void} 711 | */ 712 | FastClick.prototype.destroy = function() { 713 | var layer = this.layer; 714 | 715 | if (deviceIsAndroid) { 716 | layer.removeEventListener('mouseover', this.onMouse, true); 717 | layer.removeEventListener('mousedown', this.onMouse, true); 718 | layer.removeEventListener('mouseup', this.onMouse, true); 719 | } 720 | 721 | layer.removeEventListener('click', this.onClick, true); 722 | layer.removeEventListener('touchstart', this.onTouchStart, false); 723 | layer.removeEventListener('touchmove', this.onTouchMove, false); 724 | layer.removeEventListener('touchend', this.onTouchEnd, false); 725 | layer.removeEventListener('touchcancel', this.onTouchCancel, false); 726 | }; 727 | 728 | 729 | /** 730 | * Check whether FastClick is needed. 731 | * 732 | * @param {Element} layer The layer to listen on 733 | */ 734 | FastClick.notNeeded = function(layer) { 735 | var metaViewport; 736 | var chromeVersion; 737 | var blackberryVersion; 738 | var firefoxVersion; 739 | 740 | // Devices that don't support touch don't need FastClick 741 | if (typeof window.ontouchstart === 'undefined') { 742 | return true; 743 | } 744 | 745 | // Chrome version - zero for other browsers 746 | chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; 747 | 748 | if (chromeVersion) { 749 | 750 | if (deviceIsAndroid) { 751 | metaViewport = document.querySelector('meta[name=viewport]'); 752 | 753 | if (metaViewport) { 754 | // Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89) 755 | if (metaViewport.content.indexOf('user-scalable=no') !== -1) { 756 | return true; 757 | } 758 | // Chrome 32 and above with width=device-width or less don't need FastClick 759 | if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) { 760 | return true; 761 | } 762 | } 763 | 764 | // Chrome desktop doesn't need FastClick (issue #15) 765 | } else { 766 | return true; 767 | } 768 | } 769 | 770 | if (deviceIsBlackBerry10) { 771 | blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/); 772 | 773 | // BlackBerry 10.3+ does not require Fastclick library. 774 | // https://github.com/ftlabs/fastclick/issues/251 775 | if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) { 776 | metaViewport = document.querySelector('meta[name=viewport]'); 777 | 778 | if (metaViewport) { 779 | // user-scalable=no eliminates click delay. 780 | if (metaViewport.content.indexOf('user-scalable=no') !== -1) { 781 | return true; 782 | } 783 | // width=device-width (or less than device-width) eliminates click delay. 784 | if (document.documentElement.scrollWidth <= window.outerWidth) { 785 | return true; 786 | } 787 | } 788 | } 789 | } 790 | 791 | // IE10 with -ms-touch-action: none or manipulation, which disables double-tap-to-zoom (issue #97) 792 | if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') { 793 | return true; 794 | } 795 | 796 | // Firefox version - zero for other browsers 797 | firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; 798 | 799 | if (firefoxVersion >= 27) { 800 | // Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896 801 | 802 | metaViewport = document.querySelector('meta[name=viewport]'); 803 | if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) { 804 | return true; 805 | } 806 | } 807 | 808 | // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version 809 | // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx 810 | if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') { 811 | return true; 812 | } 813 | 814 | return false; 815 | }; 816 | 817 | 818 | /** 819 | * Factory method for creating a FastClick object 820 | * 821 | * @param {Element} layer The layer to listen on 822 | * @param {Object} [options={}] The options to override the defaults 823 | */ 824 | FastClick.attach = function(layer, options) { 825 | return new FastClick(layer, options); 826 | }; 827 | 828 | 829 | if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { 830 | 831 | // AMD. Register as an anonymous module. 832 | define(function() { 833 | return FastClick; 834 | }); 835 | } else if (typeof module !== 'undefined' && module.exports) { 836 | module.exports = FastClick.attach; 837 | module.exports.FastClick = FastClick; 838 | } else { 839 | window.FastClick = FastClick; 840 | } 841 | }()); 842 | -------------------------------------------------------------------------------- /libs/imgs/001_electric.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/001_electric.jpg -------------------------------------------------------------------------------- /libs/imgs/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/book.jpg -------------------------------------------------------------------------------- /libs/imgs/bookall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/bookall.png -------------------------------------------------------------------------------- /libs/imgs/icon-earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/icon-earth.png -------------------------------------------------------------------------------- /libs/imgs/icon-water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/icon-water.png -------------------------------------------------------------------------------- /libs/imgs/lightray_red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/lightray_red.jpg -------------------------------------------------------------------------------- /libs/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/logo.png -------------------------------------------------------------------------------- /libs/imgs/page2back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/page2back.png -------------------------------------------------------------------------------- /libs/imgs/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/photo.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/negx.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/negy.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/negz.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/posx.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/posy.jpg -------------------------------------------------------------------------------- /libs/imgs/skybox/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/skybox/posz.jpg -------------------------------------------------------------------------------- /libs/imgs/up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/up.jpg -------------------------------------------------------------------------------- /libs/imgs/users/niu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/users/niu.png -------------------------------------------------------------------------------- /libs/imgs/users/xigua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/users/xigua.png -------------------------------------------------------------------------------- /libs/imgs/water1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/water1.jpg -------------------------------------------------------------------------------- /libs/imgs/water2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/imgs/water2.jpg -------------------------------------------------------------------------------- /libs/index.js: -------------------------------------------------------------------------------- 1 | /** 页面逻辑相关参数 **/ 2 | const users = [ 3 | { n: "Logic", s: 10 }, 4 | { n: "士大夫", w: "大赞", s: 5 }, 5 | { n: "Citrus", s: 2 }, 6 | { n: "温度℃", s: 1 }, 7 | { n: "Hodor", s: 10.24 }, 8 | { n: "企鹅丫丫AVON", s: 1 }, 9 | { 10 | n: "太苦特级方糖", 11 | w: "希望捐给猫猫", 12 | s: 1, 13 | }, 14 | { n: "天将明", w: "探索未知", s: 5 }, 15 | { n: "王宇晗", s: 1 }, 16 | { n: "leooo", s: 0.1 }, 17 | { n: "~科24~", s: 0.01 }, 18 | { 19 | n: ["libs/imgs/users/niu.png"], 20 | s: 1, 21 | }, 22 | { n: "moleQ", w: "我又来了", s: 1.01 }, 23 | { n: ["西瓜丸子", "libs/imgs/users/xigua.png"], s: 1 }, 24 | { n: "呐-是小中", w: "66", s: 0.01 }, 25 | { n: "卡斯特梅的雨", s: 20 }, 26 | { n: "氕氘氚", s: 1 }, 27 | { n: "真羽", s: 1 }, 28 | { n: "veer", s: 0.01 }, 29 | { 30 | n: "浮生六记", 31 | w: "ETO成员前来报告", 32 | s: 1, 33 | }, 34 | { n: "Z", w: "Hala Madrid", s: 1 }, 35 | { n: "小张", s: 1 }, 36 | { n: "小朋友真好吃", w: "么么哒", s: 0.1 }, 37 | { n: "Alnitak", w: "THE WARTERDROP", s: 10.24 }, 38 | { n: "HK", s: 10.25 }, 39 | { n: "Z", w: "现实与幻想并存", s: 1 }, 40 | { n: "希望重燃.", s: 10 }, 41 | { n: "今天明天吃什么呢", w: "专升本复习完善成个人笔记最好", s: 5 }, 42 | { n: "科捷智能科技-葛晋 Momiji", s: 66.6 }, 43 | { n: "dragon.", w: "加油鸭", s: 0.01 }, 44 | { n: "CoderWangx", w: "水滴很酷", s: 6.66 }, 45 | { n: "Doggy", w: "太牛了woc", s: 1 }, 46 | { n: "Mr O'G桑", s: 1 }, 47 | { n: "傅Fu", w: "需要批发二向箔", s: 3 }, 48 | { n: "我的网名十二个字不信你数", s: 5 }, 49 | { n: "把海弄干的鱼", s: 5 }, 50 | { n: "岁寒", s: 10, w: " " }, 51 | { n: "青城山下㇏", s: 0.01, w: "冲鸭!孟照森!" }, 52 | { n: "T-800", s: 20 }, 53 | { n: "Roland", s: 1, w: "消灭人类暴政,世界属于三体!" }, 54 | { n: "欢乐正前方", s: 6.66, w: "加油!" }, 55 | { n: "Rose Dry Leaf Gas", s: 10, w: "我们是同志了" }, 56 | { n: "邶陂以北", s: 0.1, w: "真就这些,不多,世界属于三体,跃迁中" }, 57 | { n: "王", s: 2 }, 58 | { n: "23:33", s: 1, w: "太酷了" }, 59 | { n: "吴", s: 2, w: "消灭人类暴政,世界属于三体" }, 60 | { n: "风", s: 10, w: "支持!" }, 61 | { n: "Asphyxia.", s: 10, w: "朝歌还会远吗?" }, 62 | ]; 63 | let loadingCount = 3; // 总共有多少资源需要加载 64 | let loadingPercent = 0; // 当前加载进度 65 | let showType = 0; // 当前在哪个阶段 66 | let animeObj = { 67 | shipSpeed: 100, // 当前飞船速度,加载百分比 68 | star_speed: -0.15, // 星空速度 69 | }; 70 | const speedDom = document.getElementById("speed"); // 显示速度的DOM,多次要用 71 | let musicPlaying = false; // 音乐是否正在播放中 72 | let musicNum = 0; // 当前播放的哪一首歌曲 73 | 74 | /** THREE相关参数 **/ 75 | let scene; // 场景 76 | let camera; // 相机 77 | let renderer; // 渲染器 78 | let cameraControls; // 镜头控制器 79 | let cubeCamera1; // 六面体相机1 模拟镜面 80 | let cubeCamera2; // 六面体相机2 模拟镜面 81 | let cubeMeterial; // 六面体相机材质 模拟镜面 82 | 83 | let water_mesh; // 飞船主体模型 84 | let cone_texture; // 光锥纹理贴图 85 | let cone_group; // 光锥集合0 86 | let cone_group1; // 光锥集合1 87 | let cone_group2; // 光锥集合2 88 | 89 | let cloud; // 低速星空1 90 | let cloud2; // 低速星空2 91 | let lineMesh; // 高速星空1 92 | let lineMesh2; // 高速星空2 93 | 94 | let tunnel; // 高速时空隧道 95 | let tunnel_texture; // 高速时空隧道纹理 96 | 97 | let skybox; // 天空盒 98 | let skybox_texture; // 天空盒纹理 6张图 99 | 100 | let ship_line; // 蓝色实线材质 101 | let ship_red_line; // 红色实线材质 102 | let line1_mesh; // 前 - 蓝色 - 实线 - 中圈 103 | let line2_mesh; // 前 - 蓝色 - 实线 - 大圈 104 | let line3_mesh; // 后 - 蓝色 - 实线 - 小圈 105 | let q4_mesh; // 前 - 红色 - 虚线 - 小圈 106 | let q5_mesh; // 后 - 红色 - 虚线 - 小圈 107 | let dash1_mesh; // 前 - 蓝色 - 虚线 - 中圈 108 | let line_group; // 3个红色三角形组 109 | 110 | let composer; // 后期处理器 111 | let effectFXAA; // 抗锯齿着色器 112 | let raycaster; // 射线 113 | let mouse; // 鼠标位置 114 | let labelRenderer; // 2D标签渲染器 115 | let title2d; // 2D标签 - 头部标题 116 | let label2d; // 2D标签 - 右侧说明 117 | let outlinePass; // 外边框 118 | 119 | /** 初始化三要素 **/ 120 | function init3boss() { 121 | scene = new THREE.Scene(); 122 | camera = new THREE.PerspectiveCamera( 123 | 75, 124 | window.innerWidth / window.innerHeight, 125 | 0.1, 126 | 1500 127 | ); 128 | renderer = new THREE.WebGLRenderer(); 129 | 130 | camera.position.set(-10, 0, -50); 131 | camera.lookAt(new THREE.Vector3(0, 0, 0)); 132 | renderer.setSize(window.innerWidth, window.innerHeight, true); 133 | renderer.setClearColor(0x000000); 134 | renderer.checkShaderErrors = false; 135 | document.getElementById("canvas-box").appendChild(renderer.domElement); 136 | } 137 | 138 | /** 初始化镜头控制器 **/ 139 | function initCameraControl() { 140 | cameraControls = new THREE.OrbitControls( 141 | camera, 142 | document.getElementById("canvas-box") 143 | ); 144 | cameraControls.target.set(0, 0, 0); 145 | cameraControls.maxDistance = 100; 146 | cameraControls.minDistance = 40; 147 | cameraControls.enableKeys = false; 148 | cameraControls.enablePan = false; 149 | cameraControls.enabled = false; // 先禁用 150 | cameraControls.update(); 151 | } 152 | 153 | /** 初始化两个六面体相机 为了模拟镜面做准备 **/ 154 | function initCubeCameras() { 155 | cubeCamera1 = new THREE.CubeCamera(1, 1500, 256); 156 | cubeCamera1.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; 157 | cubeCamera1.position.x = 1; 158 | scene.add(cubeCamera1); 159 | cubeCamera2 = new THREE.CubeCamera(1, 1500, 256); 160 | cubeCamera2.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter; 161 | cubeCamera2.position.x = 1; 162 | scene.add(cubeCamera2); 163 | // 通过相机的画面作为贴图 164 | cubeMeterial = new THREE.MeshBasicMaterial({ 165 | envMap: cubeCamera2.renderTarget.texture, 166 | }); 167 | } 168 | 169 | /** 创建水滴探测器Obj 以及上面的光锥**/ 170 | function initWaterShip() { 171 | const points = []; 172 | const lang = 41; 173 | for (let i = 0; i < lang; i += 0.005) { 174 | if (i < 1) { 175 | const y = Deri.start(i); 176 | points.push(new THREE.Vector2(y, i)); 177 | } else if ((i * 1000).toFixed(2) % 2) { 178 | const y = Deri.start(i); 179 | points.push(new THREE.Vector2(y, i)); 180 | i += 0.03; 181 | } 182 | } 183 | 184 | const water_m = new THREE.LatheBufferGeometry(points, 20); 185 | water_mesh = new THREE.Mesh(water_m, cubeMeterial); 186 | water_mesh.rotation.set(0, 0, Math.PI / 2); 187 | water_mesh.position.x = 15; 188 | 189 | cone_group = new THREE.Group(); 190 | 191 | for (let i = 100; i < points.length - 100; i += 100) { 192 | const p = createCone(points[i]); 193 | p.plane1.scale.set(0.01, 0.01, 0.01); 194 | p.plane2.scale.set(0.01, 0.01, 0.01); 195 | cone_group.add(p.plane1); 196 | cone_group.add(p.plane2); 197 | } 198 | 199 | cone_group1 = cone_group.clone(); 200 | cone_group2 = cone_group.clone(); 201 | cone_group1.rotation.y = (Math.PI / 180) * 120; 202 | cone_group2.rotation.y = (Math.PI / 180) * -120; 203 | 204 | water_mesh.add(cone_group); 205 | water_mesh.add(cone_group1); 206 | water_mesh.add(cone_group2); 207 | 208 | scene.add(water_mesh); 209 | } 210 | 211 | /** 生成单个光锥方法 **/ 212 | function createCone(position) { 213 | const material = new THREE.MeshBasicMaterial({ 214 | // 基本材质 215 | map: cone_texture, 216 | transparent: true, 217 | depthTest: true, 218 | side: THREE.DoubleSide, 219 | blending: THREE.AdditiveBlending, 220 | }); 221 | const height = 1.8; 222 | const width = 0.5; 223 | const geometry = new THREE.PlaneGeometry(width, height); 224 | 225 | const plane1 = new THREE.Mesh(geometry, material); 226 | plane1.rotation.z = (Math.PI / 180) * 90; 227 | plane1.position.y = height * 100; 228 | 229 | const plane2 = plane1.clone(); 230 | plane2.rotation.x = (Math.PI / 180) * 90; 231 | 232 | plane1.position.copy( 233 | new THREE.Vector3(position.x + height / 2, position.y, 0) 234 | ); 235 | plane2.position.copy( 236 | new THREE.Vector3(position.x + height / 2, position.y, 0) 237 | ); 238 | 239 | return { plane1: plane1, plane2: plane2 }; 240 | } 241 | 242 | /** 慢速星空 - 创建star纹理 **/ 243 | function makeStarTexture() { 244 | const canvas = document.createElement("canvas"); 245 | canvas.width = 16; 246 | canvas.height = 16; 247 | const pen = canvas.getContext("2d"); 248 | const gradient = pen.createRadialGradient( 249 | canvas.width / 2, 250 | canvas.height / 2, 251 | 0, 252 | canvas.width / 2, 253 | canvas.height / 2, 254 | canvas.width / 2 255 | ); 256 | gradient.addColorStop(0, "rgba(255,255,255,1)"); 257 | gradient.addColorStop(0.2, "rgba(0,255,255,1)"); 258 | gradient.addColorStop(0.4, "rgba(0,0,164,1)"); 259 | gradient.addColorStop(1, "rgba(0,0,0,1)"); 260 | pen.fillStyle = gradient; //将笔触填充色设置为这个渐变放射状 261 | pen.fillRect(0, 0, canvas.width, canvas.height); //画矩形 262 | const texture = new THREE.Texture(canvas); //生成贴图对象(参数是图片或canvas画布) 263 | texture.needsUpdate = true; //将这个对象缓存到GPU 264 | return texture; 265 | } 266 | 267 | /** 星空 - 创建星空粒子系统 低速星空和高速星空**/ 268 | function initStarSky() { 269 | /** 低速星空 **/ 270 | //粒子材质 271 | const material1 = new THREE.PointsMaterial({ 272 | color: 0xffffff, 273 | size: 3, 274 | transparent: true, 275 | blending: THREE.AdditiveBlending, 276 | map: makeStarTexture(), 277 | }); 278 | 279 | const geom = new THREE.Geometry(); 280 | const range = 700; // 横向范围 281 | const rangex = 2000; // 纵向范围 282 | const offset = 15; // 补偿,为了不碰到飞船 283 | const color = new THREE.Color(0x00ffcc); 284 | for (let i = 0; i < 8000; i++) { 285 | let y = Math.random() * range - range / 2; 286 | let z = Math.random() * range - range / 2; 287 | if (y < offset && z < offset && y > -offset && z > -offset) { 288 | const r = Math.random() * (range / 2 - offset) + offset; 289 | y = y > 0 ? y + r : y - r; 290 | z = z > 0 ? z + r : z - r; 291 | } 292 | 293 | const particle = new THREE.Vector3( 294 | Math.random() * rangex - rangex / 2, 295 | y, 296 | z 297 | ); 298 | geom.vertices.push(particle); 299 | geom.colors.push(color); 300 | } 301 | 302 | // 创建粒子系统,相当于abc中的c 303 | cloud = new THREE.Points(geom, material1); //参数是形状和材质 304 | cloud2 = new THREE.Points(geom, material1); // 创建第2个 305 | 306 | cloud.sortParticles = true; //粒子重新排序 307 | cloud2.sortParticles = true; 308 | cloud2.position.x = 2000; 309 | scene.add(cloud); 310 | scene.add(cloud2); 311 | 312 | /** 高速星空 **/ 313 | const geometry = new THREE.BufferGeometry(); 314 | const points = []; 315 | /** 定义顶点 **/ 316 | for (let i = 0; i < 1000; i++) { 317 | const x = Math.random() * 3000 - 1500; 318 | let y = Math.random() * 500 - 250; 319 | let z = Math.random() * 500 - 250; 320 | if (y < offset && z < offset && y > -offset && z > -offset) { 321 | const r = Math.random() * (250 - offset) + offset; 322 | y = y > 0 ? y + r : y - r; 323 | z = z > 0 ? z + r : z - r; 324 | } 325 | 326 | const lang = Math.random() * 100 + 200; 327 | points.push(x, y, z, x + lang, y, z); 328 | } 329 | 330 | geometry.addAttribute( 331 | "position", 332 | new THREE.Float32BufferAttribute(points, 3) 333 | ); // 设置顶点们 334 | 335 | const material2 = new THREE.LineBasicMaterial({ 336 | color: 0x638daf, 337 | linewidth: 1, 338 | linecap: "square", 339 | linejoin: "bevel", 340 | }); 341 | lineMesh = new THREE.LineSegments(geometry, material2); 342 | lineMesh2 = lineMesh.clone(); 343 | lineMesh2.position.x = 3000; 344 | lineMesh.visible = false; 345 | lineMesh2.visible = false; 346 | scene.add(lineMesh); 347 | scene.add(lineMesh2); 348 | } 349 | 350 | /** 创建高速时空隧道 **/ 351 | function initTunne() { 352 | const tunnel_points = []; 353 | for (let i = 0; i <= 4; i += 1) { 354 | if (i === 0 || i === 4) { 355 | tunnel_points.push(new THREE.Vector3(1, 600 * i)); 356 | } 357 | tunnel_points.push(new THREE.Vector3(100, 600 * i)); 358 | } 359 | const tunnel_geometry = new THREE.LatheBufferGeometry(tunnel_points, 20); 360 | 361 | tunnel_texture.wrapT = tunnel_texture.wrapS = THREE.RepeatWrapping; 362 | tunnel_texture.repeat.set(6, 6); 363 | 364 | const tunnel_material = new THREE.MeshBasicMaterial({ 365 | side: THREE.BackSide, 366 | transparent: true, 367 | alphaMap: tunnel_texture, 368 | color: 0x3333aa, 369 | opacity: 0.1, 370 | }); 371 | 372 | tunnel = new THREE.Mesh(tunnel_geometry, tunnel_material); 373 | tunnel.rotation.z = (Math.PI / 180) * 90; 374 | tunnel.position.x = 1200; 375 | tunnel.visible = false; 376 | scene.add(tunnel); 377 | } 378 | 379 | /** 创建天空盒 **/ 380 | function initSkyBox() { 381 | const shader = THREE.ShaderLib["cube"]; 382 | shader.uniforms["tCube"].value = skybox_texture; 383 | shader.uniforms["opacity"] = { value: 1 }; 384 | const material = new THREE.ShaderMaterial({ 385 | fragmentShader: shader.fragmentShader, 386 | vertexShader: shader.vertexShader, 387 | uniforms: shader.uniforms, 388 | transparent: false, 389 | depthWrite: false, 390 | side: THREE.BackSide, 391 | }); 392 | 393 | skybox = new THREE.Mesh(new THREE.BoxGeometry(2000, 2000, 2000), material); 394 | scene.add(skybox); 395 | } 396 | 397 | /** 飞船附加物 **/ 398 | function initAppendage() { 399 | // 创建了一个圆圈的顶点坐标 400 | const ship_g = new THREE.Geometry(); 401 | for (let j = 0; j < Math.PI * 2.01; j += (2 * Math.PI) / 180) { 402 | let v = new THREE.Vector3(Math.cos(j), Math.sin(j), 0); 403 | ship_g.vertices.push(v); 404 | } 405 | const line = new MeshLine(); 406 | line.setGeometry(ship_g); 407 | 408 | // 蓝色实线材质 需要全局 409 | ship_line = new MeshLineMaterial({ 410 | color: new THREE.Color(0x70c1b3), 411 | lineWidth: 0.05, 412 | transparent: true, 413 | opacity: 0, 414 | side: THREE.DoubleSide, 415 | }); 416 | 417 | // 红色实线材质 需要全局 418 | ship_red_line = new MeshLineMaterial({ 419 | color: new THREE.Color(0xff4308), 420 | transparent: true, 421 | opacity: 0, 422 | lineWidth: 0.1, 423 | }); 424 | 425 | // 蓝色虚线材质 426 | const ship_dash = new MeshLineMaterial({ 427 | color: new THREE.Color(0x70c1b3), 428 | lineWidth: 0.05, 429 | dashArray: 0.333, 430 | dashRatio: 0.666, 431 | opacity: 0, 432 | transparent: true, 433 | side: THREE.DoubleSide, 434 | }); 435 | 436 | // 红色虚线材质 437 | const ship_red_dash = new MeshLineMaterial({ 438 | color: new THREE.Color(0xff4308), 439 | dashArray: 0.5, 440 | dashRatio: 0.5, 441 | transparent: true, 442 | lineWidth: 0.05, 443 | }); 444 | 445 | // 前 - 蓝色 - 实线 - 中圈 446 | line1_mesh = new THREE.Mesh(line.geometry, ship_line); 447 | line1_mesh.scale.set(0.01, 0.01, 0.01); // 3,3,3 448 | line1_mesh.rotation.y = (Math.PI / 180) * 90; 449 | line1_mesh.position.x = 10; // 20 450 | scene.add(line1_mesh); 451 | 452 | // 前 - 蓝色 - 实线 - 大圈 453 | line2_mesh = line1_mesh.clone(); 454 | line2_mesh.scale.set(0.01, 0.01, 0.01); // 8,8,8 455 | line2_mesh.position.x = 10; // 17 456 | scene.add(line2_mesh); 457 | 458 | // 后 - 蓝色 - 实线 - 小圈 459 | line3_mesh = line1_mesh.clone(); 460 | line3_mesh.scale.set(0.01, 0.01, 0.01); // 1.5,1.5,1.5 461 | line3_mesh.position.x = -13; // -33 462 | scene.add(line3_mesh); 463 | 464 | // 前 - 红色 - 虚线 - 小圈 465 | q4_mesh = new THREE.Mesh(line.geometry, ship_red_dash); 466 | q4_mesh.scale.set(0.01, 0.01, 0.01); // 1.5, 1.5, 1.5 467 | q4_mesh.rotation.y = (Math.PI / 180) * 90; 468 | q4_mesh.position.x = 6; // 16 469 | scene.add(q4_mesh); 470 | 471 | // 后 - 红色 - 虚线 - 小圈 472 | q5_mesh = q4_mesh.clone(); 473 | q5_mesh.scale.set(0.01, 0.01, 0.01); // 1.3,1.3,1.3 474 | q5_mesh.position.x = -13.5; // -33.5 475 | scene.add(q5_mesh); 476 | 477 | // 前 - 蓝色 - 虚线 - 中圈 478 | dash1_mesh = new THREE.Mesh(line.geometry, ship_dash); 479 | dash1_mesh.scale.set(4, 4, 4); // 3.3, 3.3, 3.3 480 | dash1_mesh.rotation.y = (Math.PI / 180) * 90; 481 | dash1_mesh.position.x = 20; 482 | scene.add(dash1_mesh); 483 | 484 | /** 3个红色三角 **/ 485 | const ship_line4_g = new THREE.Geometry(); 486 | ship_line4_g.vertices.push(new THREE.Vector3(0, 7, 0)); 487 | ship_line4_g.vertices.push(new THREE.Vector3(0, 5, 0)); 488 | 489 | const line4 = new MeshLine(); 490 | line4.setGeometry(ship_line4_g, function (p) { 491 | return 1 - p; 492 | }); 493 | 494 | const line4_mesh = new THREE.Mesh(line4.geometry, ship_red_line); 495 | line4_mesh.position.set(17, 0, 0); 496 | 497 | const line5_mesh = line4_mesh.clone(); 498 | const line6_mesh = line4_mesh.clone(); 499 | 500 | line5_mesh.rotation.x = (Math.PI / 180) * 120; 501 | line6_mesh.rotation.x = (Math.PI / 180) * 240; 502 | 503 | line_group = new THREE.Group(); 504 | line_group.add(line4_mesh); 505 | line_group.add(line5_mesh); 506 | line_group.add(line6_mesh); 507 | scene.add(line_group); 508 | } 509 | 510 | /** 初始化2D铭牌 **/ 511 | function init2dLabel() { 512 | // 顶部标签 513 | const label1Div = document.createElement("div"); 514 | label1Div.className = "title2d"; 515 | label1Div.id = "title2d"; 516 | label1Div.innerHTML = 517 | '
    Waterdrop
    '; 518 | 519 | const label1 = new THREE.CSS2DObject(label1Div); 520 | label1.position.set(15, 15, 0); // 将其坐标设置为原点偏上 521 | water_mesh.add(label1); 522 | 523 | // 后部标签 524 | const label2Div = document.createElement("div"); 525 | label2Div.className = "label2"; 526 | label2Div.id = "label2"; 527 | label2Div.innerHTML = 528 | '

    这是2.0版本的"水滴"

    配置了曲率驱动引擎及强互作用力外壳

    由半人马星座α星系朝着太阳系行进

    4个地球年后抵达

    国家天文台已能捕获其图像

    '; 529 | const label2 = new THREE.CSS2DObject(label2Div); 530 | label2.position.set(10, 50, 0); 531 | water_mesh.add(label2); 532 | } 533 | 534 | /** 初始化所有后处理特效 **/ 535 | function initComposer() { 536 | /** 铭牌渲染器 **/ 537 | labelRenderer = new THREE.CSS2DRenderer(); 538 | labelRenderer.setSize(window.innerWidth, window.innerHeight); 539 | labelRenderer.domElement.style.position = "absolute"; 540 | labelRenderer.domElement.style.top = 0; 541 | document.getElementById("canvas-box").appendChild(labelRenderer.domElement); 542 | 543 | /** 后处理 - 后期渲染器 **/ 544 | composer = new THREE.EffectComposer(renderer); 545 | var renderPass = new THREE.RenderPass(scene, camera); 546 | composer.addPass(renderPass); 547 | 548 | /** 后处理 - 外边框 **/ 549 | outlinePass = new THREE.OutlinePass( 550 | new THREE.Vector2(window.innerWidth, window.innerHeight), 551 | scene, 552 | camera 553 | ); 554 | outlinePass.edgeStrength = 0.1; // 0就看不见了 555 | outlinePass.edgeGlow = 1; 556 | outlinePass.edgeThickness = 3; 557 | outlinePass.selectedObjects = [water_mesh]; 558 | composer.addPass(outlinePass); 559 | 560 | /** 后处理 - 抗锯齿 **/ 561 | effectFXAA = new THREE.ShaderPass(THREE.FXAAShader); 562 | effectFXAA.uniforms["resolution"].value.set( 563 | 1 / window.innerWidth, 564 | 1 / window.innerHeight 565 | ); 566 | effectFXAA.renderToScreen = true; 567 | composer.addPass(effectFXAA); 568 | 569 | /** 后处理 - 屏幕闪动 **/ 570 | glitchPass = new THREE.GlitchPass(1); 571 | glitchPass.renderToScreen = true; 572 | glitchPass.goWild = false; 573 | } 574 | 575 | /** 初始化射线相关 **/ 576 | function initRaycaster() { 577 | raycaster = new THREE.Raycaster(); 578 | mouse = new THREE.Vector2(10000, 10000); 579 | } 580 | 581 | /** 鼠标移动记录位置 **/ 582 | function onMouseMove(e) { 583 | mouse.x = (e.clientX / window.innerWidth) * 2 - 1; 584 | mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; 585 | } 586 | 587 | function checkLoading() { 588 | loadingPercent += 1; 589 | speedDom.innerText = ((loadingPercent / loadingCount) * 100).toFixed(2); 590 | if (loadingPercent >= loadingCount) { 591 | init(); 592 | } 593 | } 594 | 595 | /** 加载所有纹理和图片 **/ 596 | function initAllTexturesAndImgs() { 597 | const loader = new THREE.TextureLoader(); 598 | const cubeLoader = new THREE.CubeTextureLoader(); 599 | loader.load( 600 | "./libs/imgs/lightray_red.jpg", 601 | function (texture) { 602 | cone_texture = texture; 603 | checkLoading(); 604 | }, 605 | null, 606 | function (err) { 607 | console.log("光锥纹理加载失败", err); 608 | } 609 | ); 610 | 611 | loader.load( 612 | "./libs/imgs/001_electric.jpg", 613 | function (texture) { 614 | tunnel_texture = texture; 615 | checkLoading(); 616 | }, 617 | null, 618 | function (err) { 619 | console.log("时空隧道纹理加载失败", err); 620 | } 621 | ); 622 | 623 | cubeLoader.load( 624 | [ 625 | "libs/imgs/skybox/posx.jpg", 626 | "libs/imgs/skybox/negx.jpg", 627 | "libs/imgs/skybox/posy.jpg", 628 | "libs/imgs/skybox/negy.jpg", 629 | "libs/imgs/skybox/posz.jpg", 630 | "libs/imgs/skybox/negz.jpg", 631 | ], 632 | function (texture) { 633 | skybox_texture = texture; 634 | checkLoading(); 635 | }, 636 | null, 637 | function (err) { 638 | console.log("天空盒纹理加载失败", err); 639 | } 640 | ); 641 | } 642 | 643 | /** 窗体大小改变时重置分辨率等参数 **/ 644 | function onResize() { 645 | camera.aspect = window.innerWidth / window.innerHeight; 646 | camera.updateProjectionMatrix(); 647 | renderer.setSize(window.innerWidth, window.innerHeight); 648 | composer.setSize(window.innerWidth, window.innerHeight); 649 | labelRenderer.setSize(window.innerWidth, window.innerHeight); 650 | effectFXAA.uniforms["resolution"].value.set( 651 | 1 / window.innerWidth, 652 | 1 / window.innerHeight 653 | ); 654 | } 655 | 656 | /** animate动画相关 **/ 657 | let count = 0; 658 | let hoverToDo = false; // 选中后,是否已经在执行霓虹灯效果 659 | let noHoverToDo = false; // 非选中后,是否需要重置霓虹灯状态 660 | let nowNum = 0; // 当前霓虹灯走到哪一个了 661 | function animate() { 662 | requestAnimationFrame(animate); 663 | animate_basic(); 664 | 665 | if (showType > 2) { 666 | // 第2阶段完毕后才开始射线 667 | animate_rayhover(); 668 | } 669 | 670 | if (showType === 2) { 671 | TWEEN.update(); 672 | if (animeObj.star_speed > -10) { 673 | animeObj.star_speed -= 0.02; 674 | } 675 | } else if (showType === 3) { 676 | if (animeObj.star_speed > -40) { 677 | // 初阶段加速 678 | animeObj.star_speed -= 0.08; 679 | tunnel.material.opacity += 0.001; 680 | outlinePass.edgeStrength += 0.005; 681 | } else { 682 | // 开始特效 683 | showType = 4; 684 | composer.addPass(glitchPass); 685 | skybox.visible = false; 686 | cloud.visible = false; 687 | cloud2.visible = false; 688 | lineMesh.visible = true; 689 | lineMesh2.visible = true; 690 | tunnel.material.opacity = 1; 691 | setTimeout(function () { 692 | showType = 5; 693 | glitchPass.renderToScreen = false; 694 | $("#ship-type-ul").css("transform", "translateY(-100px)"); 695 | speedRipple(); 696 | }, 400); 697 | } 698 | skybox.position.x -= 2; 699 | skybox.scale.x += 2; 700 | } else if (showType === 5) { 701 | if (outlinePass.edgeStrength > 0.05) { 702 | outlinePass.edgeStrength -= 0.01; 703 | } else { 704 | showType = 6; 705 | } 706 | } 707 | 708 | if (count % 2 === 0) { 709 | cubeMeterial.envMap = cubeCamera1.renderTarget.texture; 710 | cubeCamera2.update(renderer, scene); 711 | } else { 712 | cubeMeterial.envMap = cubeCamera2.renderTarget.texture; 713 | cubeCamera1.update(renderer, scene); 714 | } 715 | count++; 716 | count = count > 1000000 ? 1 : count; 717 | 718 | labelRenderer.render(scene, camera); 719 | if (showType === 6) { 720 | renderer.render(scene, camera); 721 | } else { 722 | composer.render(); 723 | } 724 | } 725 | 726 | // 速度脉动 727 | function speedRipple() { 728 | animeObj.shipSpeed = 729 | animeObj.shipSpeed > 1079252848.7 ? 1079252848.7 : 1079252848.8; 730 | speedDom.innerText = animeObj.shipSpeed.toFixed(2); 731 | setTimeout(speedRipple, Math.random() * 600 + 100); 732 | } 733 | // 基本运动 734 | function animate_basic() { 735 | line_group.rotation.x += 0.01; 736 | dash1_mesh.rotation.x -= 0.01; 737 | cloud.position.x += animeObj.star_speed; // 低速星空速度 738 | cloud2.position.x += animeObj.star_speed; 739 | lineMesh.position.x -= 40; 740 | lineMesh2.position.x -= 40; 741 | q4_mesh.rotation.x -= 0.04; 742 | q5_mesh.rotation.x -= 0.04; 743 | tunnel_texture.offset.x += 0.01; 744 | tunnel_texture.offset.y -= 0.06; 745 | 746 | if (cloud.position.x <= -1500) { 747 | cloud.position.x = 2500; 748 | } 749 | if (cloud2.position.x <= -1500) { 750 | cloud2.position.x = 2500; 751 | } 752 | if (lineMesh.position.x <= -3000) { 753 | lineMesh.position.x = 3000; 754 | } 755 | if (lineMesh2.position.x <= -3000) { 756 | lineMesh2.position.x = 3000; 757 | } 758 | } 759 | 760 | // 射线hover效果处理 761 | function animate_rayhover() { 762 | raycaster.setFromCamera(mouse, camera); 763 | if (raycaster.intersectObjects([water_mesh]).length) { 764 | // 被选中 765 | title2d && !title2d.hasClass("show") && title2d.addClass("show"); 766 | label2d && !label2d.hasClass("show") && label2d.addClass("show"); 767 | // 前面中圈 768 | if (line1_mesh.position.x < 20) { 769 | line1_mesh.position.x += 1; 770 | } 771 | if (line1_mesh.scale.x < 3) { 772 | const scale = line1_mesh.scale.x + 0.1; 773 | line1_mesh.scale.set(scale, scale, scale); 774 | } 775 | // 前面大圈 776 | if (line2_mesh.position.x < 17) { 777 | line2_mesh.position.x += 1; 778 | } 779 | if (line2_mesh.scale.x < 8) { 780 | const scale = line2_mesh.scale.x + 0.4; 781 | line2_mesh.scale.set(scale, scale, scale); 782 | } 783 | // 后面小圈 784 | if (line3_mesh.position.x > -23) { 785 | line3_mesh.position.x -= 1; 786 | } 787 | if (line3_mesh.scale.x < 1.5) { 788 | const scale = (line3_mesh.scale.x += 0.1); 789 | line3_mesh.scale.set(scale, scale, scale); 790 | } 791 | // 前面红色小圈 792 | if (q4_mesh.position.x < 16) { 793 | q4_mesh.position.x += 1; 794 | } 795 | if (q4_mesh.scale.x < 1.5) { 796 | const scale = (q4_mesh.scale.x += 0.1); 797 | q4_mesh.scale.set(scale, scale, scale); 798 | } 799 | // 后面红色小圈 800 | if (q5_mesh.position.x > -23.5) { 801 | q5_mesh.position.x -= 1; 802 | } 803 | if (q5_mesh.scale.x < 1.5) { 804 | const scale = (q5_mesh.scale.x += 0.1); 805 | q5_mesh.scale.set(scale, scale, scale); 806 | } 807 | // 前面虚线中圈 808 | if (dash1_mesh.material.uniforms.opacity.value < 1) { 809 | dash1_mesh.material.uniforms.opacity.value += 0.02; 810 | } 811 | if (dash1_mesh.scale.x > 3.3) { 812 | const scale = (dash1_mesh.scale.x -= 0.1); 813 | dash1_mesh.scale.set(scale, scale, scale); 814 | } 815 | // 三个三角形的材质 816 | if (ship_red_line.uniforms.opacity.value < 1) { 817 | ship_red_line.uniforms.opacity.value += 0.01; 818 | } 819 | // 实现蓝色线圈材质 820 | if (ship_line.uniforms.opacity.value < 1) { 821 | ship_line.uniforms.opacity.value = 1; 822 | } 823 | 824 | /** 被选中,处理cone_group **/ 825 | if (count % 200 === 0 && !hoverToDo) { 826 | hoverToDo = true; 827 | noHoverToDo = true; 828 | nowNum = 0; 829 | } 830 | if (hoverToDo) { 831 | nowNum += 0.15; 832 | const now = Math.floor(nowNum) * 2; 833 | 834 | if (now > cone_group.children.length + 2) { 835 | hoverToDo = false; 836 | } 837 | 838 | for (let i = 0; i < cone_group.children.length; i += 2) { 839 | if (i === now && cone_group.children[now].scale.x < 1) { 840 | const scale = cone_group.children[now].scale.x + 0.1; 841 | cone_group.children[now].scale.set(scale, 1, scale); 842 | cone_group.children[now + 1].scale.set(scale, 1, scale); 843 | cone_group1.children[now].scale.set(scale, 1, scale); 844 | cone_group1.children[now + 1].scale.set(scale, 1, scale); 845 | cone_group2.children[now].scale.set(scale, 1, scale); 846 | cone_group2.children[now + 1].scale.set(scale, 1, scale); 847 | } else if (i !== now && cone_group.children[i].scale.x > 0) { 848 | const scale = cone_group.children[i].scale.x - 0.05; 849 | cone_group.children[i].scale.set(scale, scale, scale); 850 | cone_group.children[i + 1].scale.set(scale, scale, scale); 851 | cone_group1.children[i].scale.set(scale, scale, scale); 852 | cone_group1.children[i + 1].scale.set(scale, scale, scale); 853 | cone_group2.children[i].scale.set(scale, scale, scale); 854 | cone_group2.children[i + 1].scale.set(scale, scale, scale); 855 | } 856 | } 857 | } 858 | } else { 859 | // 未被选中 860 | title2d && title2d.hasClass("show") && title2d.removeClass("show"); 861 | label2d && label2d.hasClass("show") && label2d.removeClass("show"); 862 | // 前面中圈 863 | if (line1_mesh.position.x > 10) { 864 | line1_mesh.position.x -= 1; 865 | } 866 | if (line1_mesh.scale.x > 0) { 867 | const scale = line1_mesh.scale.x - 0.1; 868 | line1_mesh.scale.set(scale, scale, scale); 869 | } 870 | // 前面大圈 871 | if (line2_mesh.position.x > 10) { 872 | line2_mesh.position.x -= 1; 873 | } 874 | if (line2_mesh.scale.x > 0) { 875 | const scale = line2_mesh.scale.x - 0.4; 876 | line2_mesh.scale.set(scale, scale, scale); 877 | } 878 | // 后面小圈 879 | if (line3_mesh.position.x < -13) { 880 | line3_mesh.position.x += 1; 881 | } 882 | if (line3_mesh.scale.x > 0) { 883 | const scale = (line3_mesh.scale.x -= 0.1); 884 | line3_mesh.scale.set(scale, scale, scale); 885 | } 886 | // 前面红色小圈 887 | if (q4_mesh.position.x > 6) { 888 | q4_mesh.position.x -= 1; 889 | } 890 | if (q4_mesh.scale.x > 0) { 891 | const scale = (q4_mesh.scale.x -= 0.1); 892 | q4_mesh.scale.set(scale, scale, scale); 893 | } 894 | // 后面红色小圈 895 | if (q5_mesh.position.x < -13.5) { 896 | q5_mesh.position.x += 1; 897 | } 898 | if (q5_mesh.scale.x > 0) { 899 | const scale = (q5_mesh.scale.x -= 0.1); 900 | q5_mesh.scale.set(scale, scale, scale); 901 | } 902 | // 前面虚线中圈 903 | if (dash1_mesh.material.uniforms.opacity.value > 0) { 904 | dash1_mesh.material.uniforms.opacity.value = 0; 905 | } 906 | if (dash1_mesh.scale.x < 7) { 907 | dash1_mesh.scale.set(7, 7, 7); 908 | } 909 | // 三个三角形的材质 910 | if (ship_red_line.uniforms.opacity.value > 0) { 911 | ship_red_line.uniforms.opacity.value = 0; 912 | } 913 | // 实现蓝色线圈材质 914 | if (ship_line.uniforms.opacity.value > 0) { 915 | ship_line.uniforms.opacity.value -= 0.05; 916 | } 917 | 918 | if (noHoverToDo) { 919 | noHoverToDo = false; 920 | hoverToDo = false; 921 | nowNum = 0; 922 | for (let i = 0; i < cone_group.children.length; i++) { 923 | cone_group.children[i].scale.set(0.001, 0.001, 0.001); 924 | cone_group1.children[i].scale.set(0.001, 0.001, 0.001); 925 | cone_group2.children[i].scale.set(0.001, 0.001, 0.001); 926 | } 927 | } 928 | } 929 | } 930 | 931 | function initNames() { 932 | users.sort(function (a, b) { 933 | return b.s - a.s; 934 | }); 935 | let str = ""; 936 | let num = 0; 937 | for (let i = 0; i < users.length; i++) { 938 | let n = ""; 939 | if (users[i].n instanceof Array) { 940 | n = users[i].n 941 | .map(function (item, index) { 942 | return item.indexOf("libs/") > -1 ? '' : item; 943 | }) 944 | .join(""); 945 | } else { 946 | n = users[i].n; 947 | } 948 | 949 | str += `
  • ${n}
    ${ 950 | users[i].w ? `
    (${users[i].w})
    ` : "" 951 | }
    ¥${users[i].s}
  • `; 952 | num += users[i].s; 953 | } 954 | 955 | const fixed = 2; 956 | let pos = num.toString().indexOf("."), 957 | decimal_places = num.toString().length - pos - 1, 958 | _int = num * Math.pow(10, decimal_places), 959 | divisor_1 = Math.pow(10, decimal_places - fixed), 960 | divisor_2 = Math.pow(10, fixed); 961 | num = Math.round(_int / divisor_1) / divisor_2; 962 | $(str).appendTo($("#names")); 963 | $("#sum").text(num); 964 | } 965 | /** 开始初始化 **/ 966 | function init() { 967 | init3boss(); // 三要素 968 | initCameraControl(); // 初始化镜头控制器 969 | initCubeCameras(); // 初始化六面体相机 970 | initWaterShip(); // 初始化水滴探测器及光锥对象 971 | init2dLabel(); // 初始化2D标签 972 | initStarSky(); // 初始化低速和高速星空 973 | initTunne(); // 初始化时空隧道 974 | initSkyBox(); // 初始化天空盒 975 | initAppendage(); // 初始化飞船附加物 976 | initComposer(); // 初始化各后期通道 977 | initRaycaster(); // 初始化射线 978 | initWords(); // 初始化标题文字 979 | initNames(); // 构造赞助者名单 980 | // 浏览器改变事件 981 | window.addEventListener("resize", onResize, false); 982 | window.addEventListener("mousemove", onMouseMove, false); 983 | FastClick.attach(document.body); 984 | labelRenderer.render(scene, camera); 985 | 986 | // 绑定音频事件 987 | $("#play-btn").on("click", function () { 988 | if (musicPlaying) { 989 | musicPlaying = false; 990 | pause(); 991 | } else { 992 | musicPlaying = true; 993 | play(); 994 | } 995 | }); 996 | $("#next-btn").on("click", next); 997 | $("#audio").on("ended", next); 998 | 999 | // 菜单事件 1000 | $("#menu-w").on("click", function (e) { 1001 | const $p = $("#page-w"); 1002 | if ($p.hasClass("show")) { 1003 | // 当前页已经出现了 1004 | $("#pages-box, #pages-box>div, #close").removeClass("show"); 1005 | } else { 1006 | $("#pages-box, #close").addClass("show"); 1007 | $("#pages-box>div").removeClass("show"); 1008 | $p.scrollTop(0).addClass("show"); 1009 | } 1010 | }); 1011 | $("#menu-m").on("click", function (e) { 1012 | const $p = $("#page-m"); 1013 | if ($p.hasClass("show")) { 1014 | // 当前页已经出现了 1015 | $("#pages-box, #pages-box>div, #close").removeClass("show"); 1016 | } else { 1017 | $("#pages-box, #close").addClass("show"); 1018 | $("#pages-box>div").removeClass("show"); 1019 | $p.scrollTop(0).addClass("show"); 1020 | } 1021 | }); 1022 | $("#menu-t").on("click", function (e) { 1023 | const $p = $("#page-t"); 1024 | if ($p.hasClass("show")) { 1025 | // 当前页已经出现了 1026 | $("#pages-box, #pages-box>div, #close").removeClass("show"); 1027 | } else { 1028 | $("#pages-box, #close").addClass("show"); 1029 | $("#pages-box>div").removeClass("show"); 1030 | $p.scrollTop(0).addClass("show"); 1031 | } 1032 | }); 1033 | $("#close").on("click", function () { 1034 | $("#pages-box, #pages-box>div, #close").removeClass("show"); 1035 | }); 1036 | 1037 | $("#pages-box").on("touchmove mousewheel DOMMouseScroll", function (e) { 1038 | e.stopPropagation(); 1039 | }); 1040 | 1041 | setTimeout(function () { 1042 | title2d = $("#title2d"); 1043 | label2d = $("#label2"); 1044 | }); 1045 | initShow(); // 开始了 1046 | 1047 | $("#ship-info-btn").addClass("show"); // s首个按钮出现 1048 | } 1049 | 1050 | /** 逻辑开始的地方 **/ 1051 | initAllTexturesAndImgs(); // 加载所有资源 1052 | 1053 | function initWords() { 1054 | const $span = $("#title>span"); 1055 | const $i = $("#title>span>i"); 1056 | for (let i = 0; i < $i.length; i++) { 1057 | const trans = Math.floor(Math.random() * 2500 + 500) + "ms"; 1058 | $span[i].style.transitionDelay = trans; 1059 | $i[i].style.transitionDelay = trans; 1060 | } 1061 | } 1062 | 1063 | // 初始化不同阶段的出现逻辑 1064 | function initShow() { 1065 | $("#ship-info-btn").on("click", function () { 1066 | var shipInfoBtn = $(this); 1067 | if (!shipInfoBtn.hasClass("show")) { 1068 | return; 1069 | } 1070 | shipInfoBtn.removeClass("show"); 1071 | const type = Number(shipInfoBtn.data("type")); 1072 | switch (type) { 1073 | case 1: // 第1阶段 1074 | show1(); 1075 | return; 1076 | case 2: // 第2阶段 常规航行 1077 | show2(); 1078 | return; 1079 | case 3: // 第3阶段 高速航行 1080 | show3(); 1081 | return; 1082 | } 1083 | }); 1084 | } 1085 | // 画面出现 1086 | function show1() { 1087 | animate(); 1088 | showType = 1; 1089 | $("#mask").fadeOut(5000, function () { 1090 | $("#menu").addClass("show"); 1091 | setTimeout(function () { 1092 | $("#ship-type-ul").css("transform", "translateY(-40px)"); 1093 | $("#ship-info-btn .btn-word").text("起航"); 1094 | $("#ship-info-btn").data("type", 2).addClass("show"); 1095 | $("#ship-info-box,#logo").css("z-index", "10"); 1096 | $("#menu li").css("transition", "all 200ms !important"); 1097 | }, 2000); 1098 | }); 1099 | $("#title-box, #logo").addClass("show"); 1100 | $("#ship-type-ul").css("transform", "translateY(-20px)"); 1101 | $("#speed-unit").text("km/h"); 1102 | 1103 | play(); 1104 | } 1105 | 1106 | // 常规推进 1107 | const CoordinateOrigin = new THREE.Vector3(0, 0, 0); 1108 | function show2() { 1109 | $("#title-box").removeClass("show"); 1110 | showType = 2; 1111 | const tween = new TWEEN.Tween({ 1112 | x: camera.position.x, 1113 | z: camera.position.z, 1114 | }) 1115 | .to( 1116 | { 1117 | x: -50, 1118 | z: -18, 1119 | }, 1120 | 4000 1121 | ) 1122 | .easing(TWEEN.Easing.Quadratic.InOut) 1123 | .onUpdate(function () { 1124 | camera.position.x = this.x; 1125 | camera.position.z = this.z; 1126 | camera.lookAt(CoordinateOrigin); 1127 | }) 1128 | .onComplete(function () { 1129 | $("#control-remind").addClass("show"); 1130 | $("#ship-info-box").addClass("pointernone"); 1131 | cameraControls.enabled = true; 1132 | showType = 2.5; // 表示第2阶段已完毕 1133 | setTimeout(function () { 1134 | $("#control-remind").removeClass("show"); 1135 | }, 5000); 1136 | setTimeout(function () { 1137 | $("#ship-info-box").removeClass("pointernone"); 1138 | $("#ship-type-ul").css("transform", "translateY(-80px)"); 1139 | $("#ship-info-btn .btn-word").text("开始星际穿梭"); 1140 | $("#ship-info-btn").data("type", 3).addClass("show"); 1141 | }, 10000); 1142 | }); 1143 | tween.start(); 1144 | anime({ 1145 | targets: animeObj, 1146 | shipSpeed: 10000, 1147 | round: 1, 1148 | easing: "linear", 1149 | duration: 4000, 1150 | update: function () { 1151 | speedDom.innerText = animeObj.shipSpeed.toFixed(2); 1152 | }, 1153 | }); 1154 | setTimeout(function () { 1155 | $("#ship-type-ul").css("transform", "translateY(-60px)"); 1156 | }, 4000); 1157 | } 1158 | 1159 | // 曲率推进 1160 | function show3() { 1161 | showType = 3; 1162 | tunnel.visible = true; 1163 | $("#ship-info-box").addClass("pointernone"); 1164 | // skybox.material.transparent = true; 1165 | // composer.addPass( outlinePass ); 1166 | anime({ 1167 | targets: animeObj, 1168 | shipSpeed: [{ value: 1079252848, duration: 12000 }], // 1079252848.8 1169 | round: 1, 1170 | easing: "linear", 1171 | update: function () { 1172 | speedDom.innerText = animeObj.shipSpeed.toFixed(2); 1173 | }, 1174 | }); 1175 | } 1176 | 1177 | /** 音频控制相关 **/ 1178 | const musics = [ 1179 | { url: "libs/music/0.mp3", title: "Fearless" }, 1180 | { url: "libs/music/1.mp3", title: "Qiu Mansion" }, 1181 | ]; 1182 | 1183 | function play() { 1184 | const audio = document.getElementById("audio"); 1185 | musicPlaying = true; 1186 | $("#play-btn i").addClass("animePlay"); 1187 | audio.play(); 1188 | } 1189 | 1190 | function pause() { 1191 | const audio = document.getElementById("audio"); 1192 | musicPlaying = false; 1193 | $("#play-btn i").removeClass("animePlay"); 1194 | audio.pause(); 1195 | } 1196 | 1197 | function next() { 1198 | const audio = document.getElementById("audio"); 1199 | let musicNow = musicNum + 1 > musics.length - 1 ? 0 : musicNum + 1; 1200 | musicNum = musicNow; 1201 | audio.pause(); 1202 | audio.src = musics[musicNow].url; 1203 | musicPlaying = true; 1204 | $("#play-btn i").addClass("animePlay"); 1205 | $("#music-name").text(musics[musicNow].title); 1206 | audio.play(); 1207 | } 1208 | -------------------------------------------------------------------------------- /libs/music/0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/music/0.mp3 -------------------------------------------------------------------------------- /libs/music/1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaLuo/water/198bfad524b5b83d856d54b1f37c8db0b6ebda82/libs/music/1.mp3 -------------------------------------------------------------------------------- /libs/three/CSS2DRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.CSS2DObject = function ( element ) { 6 | 7 | THREE.Object3D.call( this ); 8 | 9 | this.element = element; 10 | this.element.style.position = 'absolute'; 11 | 12 | this.addEventListener( 'removed', function ( event ) { 13 | 14 | if ( this.element.parentNode !== null ) { 15 | 16 | this.element.parentNode.removeChild( this.element ); 17 | 18 | } 19 | 20 | } ); 21 | 22 | }; 23 | 24 | THREE.CSS2DObject.prototype = Object.create( THREE.Object3D.prototype ); 25 | THREE.CSS2DObject.prototype.constructor = THREE.CSS2DObject; 26 | 27 | // 28 | 29 | THREE.CSS2DRenderer = function () { 30 | 31 | console.log( 'THREE.CSS2DRenderer', THREE.REVISION ); 32 | 33 | var _width, _height; 34 | var _widthHalf, _heightHalf; 35 | 36 | var vector = new THREE.Vector3(); 37 | var viewMatrix = new THREE.Matrix4(); 38 | var viewProjectionMatrix = new THREE.Matrix4(); 39 | 40 | var domElement = document.createElement( 'div' ); 41 | domElement.style.overflow = 'hidden'; 42 | 43 | this.domElement = domElement; 44 | 45 | this.getSize = function () { 46 | 47 | return { 48 | width: _width, 49 | height: _height 50 | }; 51 | 52 | }; 53 | 54 | this.setSize = function ( width, height ) { 55 | 56 | _width = width; 57 | _height = height; 58 | 59 | _widthHalf = _width / 2; 60 | _heightHalf = _height / 2; 61 | 62 | domElement.style.width = width + 'px'; 63 | domElement.style.height = height + 'px'; 64 | 65 | }; 66 | 67 | var renderObject = function ( object, camera ) { 68 | 69 | if ( object instanceof THREE.CSS2DObject ) { 70 | 71 | vector.setFromMatrixPosition( object.matrixWorld ); 72 | vector.applyMatrix4( viewProjectionMatrix ); 73 | 74 | var element = object.element; 75 | var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - vector.y * _heightHalf + _heightHalf ) + 'px)'; 76 | 77 | element.style.WebkitTransform = style; 78 | element.style.MozTransform = style; 79 | element.style.oTransform = style; 80 | element.style.transform = style; 81 | 82 | if ( element.parentNode !== domElement ) { 83 | 84 | domElement.appendChild( element ); 85 | 86 | } 87 | 88 | } 89 | 90 | for ( var i = 0, l = object.children.length; i < l; i ++ ) { 91 | 92 | renderObject( object.children[ i ], camera ); 93 | 94 | } 95 | 96 | }; 97 | 98 | this.render = function ( scene, camera ) { 99 | 100 | scene.updateMatrixWorld(); 101 | 102 | if ( camera.parent === null ) camera.updateMatrixWorld(); 103 | 104 | viewMatrix.copy( camera.matrixWorldInverse ); 105 | viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix ); 106 | 107 | renderObject( scene, camera ); 108 | 109 | }; 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /libs/three/CopyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Full-screen textured quad shader 5 | */ 6 | 7 | THREE.CopyShader = { 8 | 9 | uniforms: { 10 | 11 | "tDiffuse": { value: null }, 12 | "opacity": { value: 1.0 } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | "varying vec2 vUv;", 19 | 20 | "void main() {", 21 | 22 | "vUv = uv;", 23 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 24 | 25 | "}" 26 | 27 | ].join( "\n" ), 28 | 29 | fragmentShader: [ 30 | 31 | "uniform float opacity;", 32 | 33 | "uniform sampler2D tDiffuse;", 34 | 35 | "varying vec2 vUv;", 36 | 37 | "void main() {", 38 | 39 | "vec4 texel = texture2D( tDiffuse, vUv );", 40 | "gl_FragColor = opacity * texel;", 41 | 42 | "}" 43 | 44 | ].join( "\n" ) 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /libs/three/DigitalGlitch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author felixturner / http://airtight.cc/ 3 | * 4 | * RGB Shift Shader 5 | * Shifts red and blue channels from center in opposite directions 6 | * Ported from http://kriss.cx/tom/2009/05/rgb-shift/ 7 | * by Tom Butterworth / http://kriss.cx/tom/ 8 | * 9 | * amount: shift distance (1 is width of input) 10 | * angle: shift angle in radians 11 | */ 12 | 13 | THREE.DigitalGlitch = { 14 | 15 | uniforms: { 16 | 17 | "tDiffuse": { value: null },//diffuse texture 18 | "tDisp": { value: null },//displacement texture for digital glitch squares 19 | "byp": { value: 0 },//apply the glitch ? 20 | "amount": { value: 0.08 }, 21 | "angle": { value: 0.02 }, 22 | "seed": { value: 0.02 }, 23 | "seed_x": { value: 0.02 },//-1,1 24 | "seed_y": { value: 0.02 },//-1,1 25 | "distortion_x": { value: 0.5 }, 26 | "distortion_y": { value: 0.6 }, 27 | "col_s": { value: 0.05 } 28 | }, 29 | 30 | vertexShader: [ 31 | 32 | "varying vec2 vUv;", 33 | "void main() {", 34 | "vUv = uv;", 35 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 36 | "}" 37 | ].join( "\n" ), 38 | 39 | fragmentShader: [ 40 | "uniform int byp;",//should we apply the glitch ? 41 | 42 | "uniform sampler2D tDiffuse;", 43 | "uniform sampler2D tDisp;", 44 | 45 | "uniform float amount;", 46 | "uniform float angle;", 47 | "uniform float seed;", 48 | "uniform float seed_x;", 49 | "uniform float seed_y;", 50 | "uniform float distortion_x;", 51 | "uniform float distortion_y;", 52 | "uniform float col_s;", 53 | 54 | "varying vec2 vUv;", 55 | 56 | 57 | "float rand(vec2 co){", 58 | "return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);", 59 | "}", 60 | 61 | "void main() {", 62 | "if(byp<1) {", 63 | "vec2 p = vUv;", 64 | "float xs = floor(gl_FragCoord.x / 0.5);", 65 | "float ys = floor(gl_FragCoord.y / 0.5);", 66 | //based on staffantans glitch shader for unity https://github.com/staffantan/unityglitch 67 | "vec4 normal = texture2D (tDisp, p*seed*seed);", 68 | "if(p.ydistortion_x-col_s*seed) {", 69 | "if(seed_x>0.){", 70 | "p.y = 1. - (p.y + distortion_y);", 71 | "}", 72 | "else {", 73 | "p.y = distortion_y;", 74 | "}", 75 | "}", 76 | "if(p.xdistortion_y-col_s*seed) {", 77 | "if(seed_y>0.){", 78 | "p.x=distortion_x;", 79 | "}", 80 | "else {", 81 | "p.x = 1. - (p.x + distortion_x);", 82 | "}", 83 | "}", 84 | "p.x+=normal.x*seed_x*(seed/5.);", 85 | "p.y+=normal.y*seed_y*(seed/5.);", 86 | //base from RGB shift shader 87 | "vec2 offset = amount * vec2( cos(angle), sin(angle));", 88 | "vec4 cr = texture2D(tDiffuse, p + offset);", 89 | "vec4 cga = texture2D(tDiffuse, p);", 90 | "vec4 cb = texture2D(tDiffuse, p - offset);", 91 | "gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);", 92 | //add noise 93 | "vec4 snow = 200.*amount*vec4(rand(vec2(xs * seed,ys * seed*50.))*0.2);", 94 | "gl_FragColor = gl_FragColor+ snow;", 95 | "}", 96 | "else {", 97 | "gl_FragColor=texture2D (tDiffuse, vUv);", 98 | "}", 99 | "}" 100 | 101 | ].join( "\n" ) 102 | 103 | }; 104 | -------------------------------------------------------------------------------- /libs/three/EffectComposer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.EffectComposer = function ( renderer, renderTarget ) { 6 | 7 | this.renderer = renderer; 8 | 9 | if ( renderTarget === undefined ) { 10 | 11 | var parameters = { 12 | minFilter: THREE.LinearFilter, 13 | magFilter: THREE.LinearFilter, 14 | format: THREE.RGBAFormat, 15 | stencilBuffer: false 16 | }; 17 | 18 | var size = renderer.getDrawingBufferSize(); 19 | renderTarget = new THREE.WebGLRenderTarget( size.width, size.height, parameters ); 20 | renderTarget.texture.name = 'EffectComposer.rt1'; 21 | 22 | } 23 | 24 | this.renderTarget1 = renderTarget; 25 | this.renderTarget2 = renderTarget.clone(); 26 | this.renderTarget2.texture.name = 'EffectComposer.rt2'; 27 | 28 | this.writeBuffer = this.renderTarget1; 29 | this.readBuffer = this.renderTarget2; 30 | 31 | this.passes = []; 32 | 33 | // dependencies 34 | 35 | if ( THREE.CopyShader === undefined ) { 36 | 37 | console.error( 'THREE.EffectComposer relies on THREE.CopyShader' ); 38 | 39 | } 40 | 41 | if ( THREE.ShaderPass === undefined ) { 42 | 43 | console.error( 'THREE.EffectComposer relies on THREE.ShaderPass' ); 44 | 45 | } 46 | 47 | this.copyPass = new THREE.ShaderPass( THREE.CopyShader ); 48 | 49 | }; 50 | 51 | Object.assign( THREE.EffectComposer.prototype, { 52 | 53 | swapBuffers: function () { 54 | 55 | var tmp = this.readBuffer; 56 | this.readBuffer = this.writeBuffer; 57 | this.writeBuffer = tmp; 58 | 59 | }, 60 | 61 | addPass: function ( pass ) { 62 | 63 | this.passes.push( pass ); 64 | 65 | var size = this.renderer.getDrawingBufferSize(); 66 | pass.setSize( size.width, size.height ); 67 | 68 | }, 69 | 70 | insertPass: function ( pass, index ) { 71 | 72 | this.passes.splice( index, 0, pass ); 73 | 74 | }, 75 | 76 | render: function ( delta ) { 77 | 78 | var maskActive = false; 79 | 80 | var pass, i, il = this.passes.length; 81 | 82 | for ( i = 0; i < il; i ++ ) { 83 | 84 | pass = this.passes[ i ]; 85 | 86 | if ( pass.enabled === false ) continue; 87 | 88 | pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive ); 89 | 90 | if ( pass.needsSwap ) { 91 | 92 | if ( maskActive ) { 93 | 94 | var context = this.renderer.context; 95 | 96 | context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 97 | 98 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta ); 99 | 100 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 101 | 102 | } 103 | 104 | this.swapBuffers(); 105 | 106 | } 107 | 108 | if ( THREE.MaskPass !== undefined ) { 109 | 110 | if ( pass instanceof THREE.MaskPass ) { 111 | 112 | maskActive = true; 113 | 114 | } else if ( pass instanceof THREE.ClearMaskPass ) { 115 | 116 | maskActive = false; 117 | 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | }, 125 | 126 | reset: function ( renderTarget ) { 127 | 128 | if ( renderTarget === undefined ) { 129 | 130 | var size = this.renderer.getDrawingBufferSize(); 131 | 132 | renderTarget = this.renderTarget1.clone(); 133 | renderTarget.setSize( size.width, size.height ); 134 | 135 | } 136 | 137 | this.renderTarget1.dispose(); 138 | this.renderTarget2.dispose(); 139 | this.renderTarget1 = renderTarget; 140 | this.renderTarget2 = renderTarget.clone(); 141 | 142 | this.writeBuffer = this.renderTarget1; 143 | this.readBuffer = this.renderTarget2; 144 | 145 | }, 146 | 147 | setSize: function ( width, height ) { 148 | 149 | this.renderTarget1.setSize( width, height ); 150 | this.renderTarget2.setSize( width, height ); 151 | 152 | for ( var i = 0; i < this.passes.length; i ++ ) { 153 | 154 | this.passes[ i ].setSize( width, height ); 155 | 156 | } 157 | 158 | } 159 | 160 | } ); 161 | 162 | 163 | THREE.Pass = function () { 164 | 165 | // if set to true, the pass is processed by the composer 166 | this.enabled = true; 167 | 168 | // if set to true, the pass indicates to swap read and write buffer after rendering 169 | this.needsSwap = true; 170 | 171 | // if set to true, the pass clears its buffer before rendering 172 | this.clear = false; 173 | 174 | // if set to true, the result of the pass is rendered to screen 175 | this.renderToScreen = false; 176 | 177 | }; 178 | 179 | Object.assign( THREE.Pass.prototype, { 180 | 181 | setSize: function ( width, height ) {}, 182 | 183 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 184 | 185 | console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); 186 | 187 | } 188 | 189 | } ); 190 | -------------------------------------------------------------------------------- /libs/three/GlitchPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.GlitchPass = function ( dt_size ) { 6 | 7 | THREE.Pass.call( this ); 8 | 9 | if ( THREE.DigitalGlitch === undefined ) console.error( "THREE.GlitchPass relies on THREE.DigitalGlitch" ); 10 | 11 | var shader = THREE.DigitalGlitch; 12 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 13 | 14 | if ( dt_size == undefined ) dt_size = 64; 15 | 16 | 17 | this.uniforms[ "tDisp" ].value = this.generateHeightmap( dt_size ); 18 | 19 | 20 | this.material = new THREE.ShaderMaterial( { 21 | uniforms: this.uniforms, 22 | vertexShader: shader.vertexShader, 23 | fragmentShader: shader.fragmentShader 24 | } ); 25 | 26 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 27 | this.scene = new THREE.Scene(); 28 | 29 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 30 | this.quad.frustumCulled = false; // Avoid getting clipped 31 | this.scene.add( this.quad ); 32 | 33 | this.goWild = false; 34 | this.curF = 0; 35 | this.generateTrigger(); 36 | 37 | }; 38 | 39 | THREE.GlitchPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 40 | 41 | constructor: THREE.GlitchPass, 42 | 43 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 44 | 45 | this.uniforms[ "tDiffuse" ].value = readBuffer.texture; 46 | this.uniforms[ 'seed' ].value = Math.random();//default seeding 47 | this.uniforms[ 'byp' ].value = 0; 48 | 49 | if ( this.curF % this.randX == 0 || this.goWild == true ) { 50 | 51 | this.uniforms[ 'amount' ].value = Math.random() / 30; 52 | this.uniforms[ 'angle' ].value = THREE.Math.randFloat( - Math.PI, Math.PI ); 53 | this.uniforms[ 'seed_x' ].value = THREE.Math.randFloat( - 1, 1 ); 54 | this.uniforms[ 'seed_y' ].value = THREE.Math.randFloat( - 1, 1 ); 55 | this.uniforms[ 'distortion_x' ].value = THREE.Math.randFloat( 0, 1 ); 56 | this.uniforms[ 'distortion_y' ].value = THREE.Math.randFloat( 0, 1 ); 57 | this.curF = 0; 58 | this.generateTrigger(); 59 | 60 | } else if ( this.curF % this.randX < this.randX / 5 ) { 61 | 62 | this.uniforms[ 'amount' ].value = Math.random() / 90; 63 | this.uniforms[ 'angle' ].value = THREE.Math.randFloat( - Math.PI, Math.PI ); 64 | this.uniforms[ 'distortion_x' ].value = THREE.Math.randFloat( 0, 1 ); 65 | this.uniforms[ 'distortion_y' ].value = THREE.Math.randFloat( 0, 1 ); 66 | this.uniforms[ 'seed_x' ].value = THREE.Math.randFloat( - 0.3, 0.3 ); 67 | this.uniforms[ 'seed_y' ].value = THREE.Math.randFloat( - 0.3, 0.3 ); 68 | 69 | } else if ( this.goWild == false ) { 70 | 71 | this.uniforms[ 'byp' ].value = 1; 72 | 73 | } 74 | 75 | this.curF ++; 76 | this.quad.material = this.material; 77 | 78 | if ( this.renderToScreen ) { 79 | 80 | renderer.render( this.scene, this.camera ); 81 | 82 | } else { 83 | 84 | renderer.render( this.scene, this.camera, writeBuffer, this.clear ); 85 | 86 | } 87 | 88 | }, 89 | 90 | generateTrigger: function() { 91 | 92 | this.randX = THREE.Math.randInt( 120, 240 ); 93 | 94 | }, 95 | 96 | generateHeightmap: function( dt_size ) { 97 | 98 | var data_arr = new Float32Array( dt_size * dt_size * 3 ); 99 | var length = dt_size * dt_size; 100 | 101 | for ( var i = 0; i < length; i ++ ) { 102 | 103 | var val = THREE.Math.randFloat( 0, 1 ); 104 | data_arr[ i * 3 + 0 ] = val; 105 | data_arr[ i * 3 + 1 ] = val; 106 | data_arr[ i * 3 + 2 ] = val; 107 | 108 | } 109 | 110 | var texture = new THREE.DataTexture( data_arr, dt_size, dt_size, THREE.RGBFormat, THREE.FloatType ); 111 | texture.needsUpdate = true; 112 | return texture; 113 | 114 | } 115 | 116 | } ); 117 | -------------------------------------------------------------------------------- /libs/three/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one-finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: two-finger move 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.panSpeed = 1.0; 63 | this.screenSpacePanning = false; // if true, pan in screen-space 64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 65 | 66 | // Set to true to automatically rotate around the target 67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 68 | this.autoRotate = false; 69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 70 | 71 | // Set to false to disable use of the keys 72 | this.enableKeys = true; 73 | 74 | // The four arrow keys 75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 76 | 77 | // Mouse buttons 78 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 79 | 80 | // for reset 81 | this.target0 = this.target.clone(); 82 | this.position0 = this.object.position.clone(); 83 | this.zoom0 = this.object.zoom; 84 | 85 | // 86 | // public methods 87 | // 88 | 89 | this.getPolarAngle = function () { 90 | 91 | return spherical.phi; 92 | 93 | }; 94 | 95 | this.getAzimuthalAngle = function () { 96 | 97 | return spherical.theta; 98 | 99 | }; 100 | 101 | this.saveState = function () { 102 | 103 | scope.target0.copy( scope.target ); 104 | scope.position0.copy( scope.object.position ); 105 | scope.zoom0 = scope.object.zoom; 106 | 107 | }; 108 | 109 | this.reset = function () { 110 | 111 | scope.target.copy( scope.target0 ); 112 | scope.object.position.copy( scope.position0 ); 113 | scope.object.zoom = scope.zoom0; 114 | 115 | scope.object.updateProjectionMatrix(); 116 | scope.dispatchEvent( changeEvent ); 117 | 118 | scope.update(); 119 | 120 | state = STATE.NONE; 121 | 122 | }; 123 | 124 | // this method is exposed, but perhaps it would be better if we can make it private... 125 | this.update = function () { 126 | 127 | var offset = new THREE.Vector3(); 128 | 129 | // so camera.up is the orbit axis 130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 131 | var quatInverse = quat.clone().inverse(); 132 | 133 | var lastPosition = new THREE.Vector3(); 134 | var lastQuaternion = new THREE.Quaternion(); 135 | 136 | return function update() { 137 | 138 | var position = scope.object.position; 139 | 140 | offset.copy( position ).sub( scope.target ); 141 | 142 | // rotate offset to "y-axis-is-up" space 143 | offset.applyQuaternion( quat ); 144 | 145 | // angle from z-axis around y-axis 146 | spherical.setFromVector3( offset ); 147 | 148 | if ( scope.autoRotate && state === STATE.NONE ) { 149 | 150 | rotateLeft( getAutoRotationAngle() ); 151 | 152 | } 153 | 154 | spherical.theta += sphericalDelta.theta; 155 | spherical.phi += sphericalDelta.phi; 156 | 157 | // restrict theta to be between desired limits 158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 159 | 160 | // restrict phi to be between desired limits 161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 162 | 163 | spherical.makeSafe(); 164 | 165 | 166 | spherical.radius *= scale; 167 | 168 | // restrict radius to be between desired limits 169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 170 | 171 | // move target to panned location 172 | scope.target.add( panOffset ); 173 | 174 | offset.setFromSpherical( spherical ); 175 | 176 | // rotate offset back to "camera-up-vector-is-up" space 177 | offset.applyQuaternion( quatInverse ); 178 | 179 | position.copy( scope.target ).add( offset ); 180 | 181 | scope.object.lookAt( scope.target ); 182 | 183 | if ( scope.enableDamping === true ) { 184 | 185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 187 | 188 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | sphericalDelta.set( 0, 0, 0 ); 193 | 194 | panOffset.set( 0, 0, 0 ); 195 | 196 | } 197 | 198 | scale = 1; 199 | 200 | // update condition is: 201 | // min(camera displacement, camera rotation in radians)^2 > EPS 202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 203 | 204 | if ( zoomChanged || 205 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 207 | 208 | scope.dispatchEvent( changeEvent ); 209 | 210 | lastPosition.copy( scope.object.position ); 211 | lastQuaternion.copy( scope.object.quaternion ); 212 | zoomChanged = false; 213 | 214 | return true; 215 | 216 | } 217 | 218 | return false; 219 | 220 | }; 221 | 222 | }(); 223 | 224 | this.dispose = function () { 225 | 226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 229 | 230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 233 | 234 | document.removeEventListener( 'mousemove', onMouseMove, false ); 235 | document.removeEventListener( 'mouseup', onMouseUp, false ); 236 | 237 | window.removeEventListener( 'keydown', onKeyDown, false ); 238 | 239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 240 | 241 | }; 242 | 243 | // 244 | // internals 245 | // 246 | 247 | var scope = this; 248 | 249 | var changeEvent = { type: 'change' }; 250 | var startEvent = { type: 'start' }; 251 | var endEvent = { type: 'end' }; 252 | 253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; 254 | 255 | var state = STATE.NONE; 256 | 257 | var EPS = 0.000001; 258 | 259 | // current position in spherical coordinates 260 | var spherical = new THREE.Spherical(); 261 | var sphericalDelta = new THREE.Spherical(); 262 | 263 | var scale = 1; 264 | var panOffset = new THREE.Vector3(); 265 | var zoomChanged = false; 266 | 267 | var rotateStart = new THREE.Vector2(); 268 | var rotateEnd = new THREE.Vector2(); 269 | var rotateDelta = new THREE.Vector2(); 270 | 271 | var panStart = new THREE.Vector2(); 272 | var panEnd = new THREE.Vector2(); 273 | var panDelta = new THREE.Vector2(); 274 | 275 | var dollyStart = new THREE.Vector2(); 276 | var dollyEnd = new THREE.Vector2(); 277 | var dollyDelta = new THREE.Vector2(); 278 | 279 | function getAutoRotationAngle() { 280 | 281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 282 | 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow( 0.95, scope.zoomSpeed ); 288 | 289 | } 290 | 291 | function rotateLeft( angle ) { 292 | 293 | sphericalDelta.theta -= angle; 294 | 295 | } 296 | 297 | function rotateUp( angle ) { 298 | 299 | sphericalDelta.phi -= angle; 300 | 301 | } 302 | 303 | var panLeft = function () { 304 | 305 | var v = new THREE.Vector3(); 306 | 307 | return function panLeft( distance, objectMatrix ) { 308 | 309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 310 | v.multiplyScalar( - distance ); 311 | 312 | panOffset.add( v ); 313 | 314 | }; 315 | 316 | }(); 317 | 318 | var panUp = function () { 319 | 320 | var v = new THREE.Vector3(); 321 | 322 | return function panUp( distance, objectMatrix ) { 323 | 324 | if ( scope.screenSpacePanning === true ) { 325 | 326 | v.setFromMatrixColumn( objectMatrix, 1 ); 327 | 328 | } else { 329 | 330 | v.setFromMatrixColumn( objectMatrix, 0 ); 331 | v.crossVectors( scope.object.up, v ); 332 | 333 | } 334 | 335 | v.multiplyScalar( distance ); 336 | 337 | panOffset.add( v ); 338 | 339 | }; 340 | 341 | }(); 342 | 343 | // deltaX and deltaY are in pixels; right and down are positive 344 | var pan = function () { 345 | 346 | var offset = new THREE.Vector3(); 347 | 348 | return function pan( deltaX, deltaY ) { 349 | 350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 351 | 352 | if ( scope.object.isPerspectiveCamera ) { 353 | 354 | // perspective 355 | var position = scope.object.position; 356 | offset.copy( position ).sub( scope.target ); 357 | var targetDistance = offset.length(); 358 | 359 | // half of the fov is center to top of screen 360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 361 | 362 | // we use only clientHeight here so aspect ratio does not distort speed 363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 365 | 366 | } else if ( scope.object.isOrthographicCamera ) { 367 | 368 | // orthographic 369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 371 | 372 | } else { 373 | 374 | // camera neither orthographic nor perspective 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 376 | scope.enablePan = false; 377 | 378 | } 379 | 380 | }; 381 | 382 | }(); 383 | 384 | function dollyIn( dollyScale ) { 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | scale /= dollyScale; 389 | 390 | } else if ( scope.object.isOrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | function dollyOut( dollyScale ) { 406 | 407 | if ( scope.object.isPerspectiveCamera ) { 408 | 409 | scale *= dollyScale; 410 | 411 | } else if ( scope.object.isOrthographicCamera ) { 412 | 413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 414 | scope.object.updateProjectionMatrix(); 415 | zoomChanged = true; 416 | 417 | } else { 418 | 419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 420 | scope.enableZoom = false; 421 | 422 | } 423 | 424 | } 425 | 426 | // 427 | // event callbacks - update the object state 428 | // 429 | 430 | function handleMouseDownRotate( event ) { 431 | 432 | //console.log( 'handleMouseDownRotate' ); 433 | 434 | rotateStart.set( event.clientX, event.clientY ); 435 | 436 | } 437 | 438 | function handleMouseDownDolly( event ) { 439 | 440 | //console.log( 'handleMouseDownDolly' ); 441 | 442 | dollyStart.set( event.clientX, event.clientY ); 443 | 444 | } 445 | 446 | function handleMouseDownPan( event ) { 447 | 448 | //console.log( 'handleMouseDownPan' ); 449 | 450 | panStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseMoveRotate( event ) { 455 | 456 | //console.log( 'handleMouseMoveRotate' ); 457 | 458 | rotateEnd.set( event.clientX, event.clientY ); 459 | 460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 461 | 462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 463 | 464 | // rotating across whole screen goes 360 degrees around 465 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth ); 466 | 467 | // rotating up and down along whole screen attempts to go 360, but limited to 180 468 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 469 | 470 | rotateStart.copy( rotateEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMoveDolly( event ) { 477 | 478 | //console.log( 'handleMouseMoveDolly' ); 479 | 480 | dollyEnd.set( event.clientX, event.clientY ); 481 | 482 | dollyDelta.subVectors( dollyEnd, dollyStart ); 483 | 484 | if ( dollyDelta.y > 0 ) { 485 | 486 | dollyIn( getZoomScale() ); 487 | 488 | } else if ( dollyDelta.y < 0 ) { 489 | 490 | dollyOut( getZoomScale() ); 491 | 492 | } 493 | 494 | dollyStart.copy( dollyEnd ); 495 | 496 | scope.update(); 497 | 498 | } 499 | 500 | function handleMouseMovePan( event ) { 501 | 502 | //console.log( 'handleMouseMovePan' ); 503 | 504 | panEnd.set( event.clientX, event.clientY ); 505 | 506 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 507 | 508 | pan( panDelta.x, panDelta.y ); 509 | 510 | panStart.copy( panEnd ); 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleMouseUp( event ) { 517 | 518 | // console.log( 'handleMouseUp' ); 519 | 520 | } 521 | 522 | function handleMouseWheel( event ) { 523 | 524 | // console.log( 'handleMouseWheel' ); 525 | 526 | if ( event.deltaY < 0 ) { 527 | 528 | dollyOut( getZoomScale() ); 529 | 530 | } else if ( event.deltaY > 0 ) { 531 | 532 | dollyIn( getZoomScale() ); 533 | 534 | } 535 | 536 | scope.update(); 537 | 538 | } 539 | 540 | function handleKeyDown( event ) { 541 | 542 | //console.log( 'handleKeyDown' ); 543 | 544 | switch ( event.keyCode ) { 545 | 546 | case scope.keys.UP: 547 | pan( 0, scope.keyPanSpeed ); 548 | scope.update(); 549 | break; 550 | 551 | case scope.keys.BOTTOM: 552 | pan( 0, - scope.keyPanSpeed ); 553 | scope.update(); 554 | break; 555 | 556 | case scope.keys.LEFT: 557 | pan( scope.keyPanSpeed, 0 ); 558 | scope.update(); 559 | break; 560 | 561 | case scope.keys.RIGHT: 562 | pan( - scope.keyPanSpeed, 0 ); 563 | scope.update(); 564 | break; 565 | 566 | } 567 | 568 | } 569 | 570 | function handleTouchStartRotate( event ) { 571 | 572 | //console.log( 'handleTouchStartRotate' ); 573 | 574 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 575 | 576 | } 577 | 578 | function handleTouchStartDollyPan( event ) { 579 | 580 | //console.log( 'handleTouchStartDollyPan' ); 581 | 582 | if ( scope.enableZoom ) { 583 | 584 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 585 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 586 | 587 | var distance = Math.sqrt( dx * dx + dy * dy ); 588 | 589 | dollyStart.set( 0, distance ); 590 | 591 | } 592 | 593 | if ( scope.enablePan ) { 594 | 595 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 596 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 597 | 598 | panStart.set( x, y ); 599 | 600 | } 601 | 602 | } 603 | 604 | function handleTouchMoveRotate( event ) { 605 | 606 | //console.log( 'handleTouchMoveRotate' ); 607 | 608 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 609 | 610 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 611 | 612 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 613 | 614 | // rotating across whole screen goes 360 degrees around 615 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth ); 616 | 617 | // rotating up and down along whole screen attempts to go 360, but limited to 180 618 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 619 | 620 | rotateStart.copy( rotateEnd ); 621 | 622 | scope.update(); 623 | 624 | } 625 | 626 | function handleTouchMoveDollyPan( event ) { 627 | 628 | //console.log( 'handleTouchMoveDollyPan' ); 629 | 630 | if ( scope.enableZoom ) { 631 | 632 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 633 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 634 | 635 | var distance = Math.sqrt( dx * dx + dy * dy ); 636 | 637 | dollyEnd.set( 0, distance ); 638 | 639 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 640 | 641 | dollyIn( dollyDelta.y ); 642 | 643 | dollyStart.copy( dollyEnd ); 644 | 645 | } 646 | 647 | if ( scope.enablePan ) { 648 | 649 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 650 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 651 | 652 | panEnd.set( x, y ); 653 | 654 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 655 | 656 | pan( panDelta.x, panDelta.y ); 657 | 658 | panStart.copy( panEnd ); 659 | 660 | } 661 | 662 | scope.update(); 663 | 664 | } 665 | 666 | function handleTouchEnd( event ) { 667 | 668 | //console.log( 'handleTouchEnd' ); 669 | 670 | } 671 | 672 | // 673 | // event handlers - FSM: listen for events and reset state 674 | // 675 | 676 | function onMouseDown( event ) { 677 | 678 | if ( scope.enabled === false ) return; 679 | 680 | event.preventDefault(); 681 | 682 | switch ( event.button ) { 683 | 684 | case scope.mouseButtons.ORBIT: 685 | 686 | if ( scope.enableRotate === false ) return; 687 | 688 | handleMouseDownRotate( event ); 689 | 690 | state = STATE.ROTATE; 691 | 692 | break; 693 | 694 | case scope.mouseButtons.ZOOM: 695 | 696 | if ( scope.enableZoom === false ) return; 697 | 698 | handleMouseDownDolly( event ); 699 | 700 | state = STATE.DOLLY; 701 | 702 | break; 703 | 704 | case scope.mouseButtons.PAN: 705 | 706 | if ( scope.enablePan === false ) return; 707 | 708 | handleMouseDownPan( event ); 709 | 710 | state = STATE.PAN; 711 | 712 | break; 713 | 714 | } 715 | 716 | if ( state !== STATE.NONE ) { 717 | 718 | document.addEventListener( 'mousemove', onMouseMove, false ); 719 | document.addEventListener( 'mouseup', onMouseUp, false ); 720 | 721 | scope.dispatchEvent( startEvent ); 722 | 723 | } 724 | 725 | } 726 | 727 | function onMouseMove( event ) { 728 | 729 | if ( scope.enabled === false ) return; 730 | 731 | event.preventDefault(); 732 | 733 | switch ( state ) { 734 | 735 | case STATE.ROTATE: 736 | 737 | if ( scope.enableRotate === false ) return; 738 | 739 | handleMouseMoveRotate( event ); 740 | 741 | break; 742 | 743 | case STATE.DOLLY: 744 | 745 | if ( scope.enableZoom === false ) return; 746 | 747 | handleMouseMoveDolly( event ); 748 | 749 | break; 750 | 751 | case STATE.PAN: 752 | 753 | if ( scope.enablePan === false ) return; 754 | 755 | handleMouseMovePan( event ); 756 | 757 | break; 758 | 759 | } 760 | 761 | } 762 | 763 | function onMouseUp( event ) { 764 | 765 | if ( scope.enabled === false ) return; 766 | 767 | handleMouseUp( event ); 768 | 769 | document.removeEventListener( 'mousemove', onMouseMove, false ); 770 | document.removeEventListener( 'mouseup', onMouseUp, false ); 771 | 772 | scope.dispatchEvent( endEvent ); 773 | 774 | state = STATE.NONE; 775 | 776 | } 777 | 778 | function onMouseWheel( event ) { 779 | 780 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 781 | 782 | event.preventDefault(); 783 | event.stopPropagation(); 784 | 785 | scope.dispatchEvent( startEvent ); 786 | 787 | handleMouseWheel( event ); 788 | 789 | scope.dispatchEvent( endEvent ); 790 | 791 | } 792 | 793 | function onKeyDown( event ) { 794 | 795 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 796 | 797 | handleKeyDown( event ); 798 | 799 | } 800 | 801 | function onTouchStart( event ) { 802 | 803 | if ( scope.enabled === false ) return; 804 | 805 | event.preventDefault(); 806 | 807 | switch ( event.touches.length ) { 808 | 809 | case 1: // one-fingered touch: rotate 810 | 811 | if ( scope.enableRotate === false ) return; 812 | 813 | handleTouchStartRotate( event ); 814 | 815 | state = STATE.TOUCH_ROTATE; 816 | 817 | break; 818 | 819 | case 2: // two-fingered touch: dolly-pan 820 | 821 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 822 | 823 | handleTouchStartDollyPan( event ); 824 | 825 | state = STATE.TOUCH_DOLLY_PAN; 826 | 827 | break; 828 | 829 | default: 830 | 831 | state = STATE.NONE; 832 | 833 | } 834 | 835 | if ( state !== STATE.NONE ) { 836 | 837 | scope.dispatchEvent( startEvent ); 838 | 839 | } 840 | 841 | } 842 | 843 | function onTouchMove( event ) { 844 | 845 | if ( scope.enabled === false ) return; 846 | 847 | event.preventDefault(); 848 | event.stopPropagation(); 849 | 850 | switch ( event.touches.length ) { 851 | 852 | case 1: // one-fingered touch: rotate 853 | 854 | if ( scope.enableRotate === false ) return; 855 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 856 | 857 | handleTouchMoveRotate( event ); 858 | 859 | break; 860 | 861 | case 2: // two-fingered touch: dolly-pan 862 | 863 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 864 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 865 | 866 | handleTouchMoveDollyPan( event ); 867 | 868 | break; 869 | 870 | default: 871 | 872 | state = STATE.NONE; 873 | 874 | } 875 | 876 | } 877 | 878 | function onTouchEnd( event ) { 879 | 880 | if ( scope.enabled === false ) return; 881 | 882 | handleTouchEnd( event ); 883 | 884 | scope.dispatchEvent( endEvent ); 885 | 886 | state = STATE.NONE; 887 | 888 | } 889 | 890 | function onContextMenu( event ) { 891 | 892 | if ( scope.enabled === false ) return; 893 | 894 | event.preventDefault(); 895 | 896 | } 897 | 898 | // 899 | 900 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 901 | 902 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 903 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 904 | 905 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 906 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 907 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 908 | 909 | window.addEventListener( 'keydown', onKeyDown, false ); 910 | 911 | // force an update at start 912 | 913 | this.update(); 914 | 915 | }; 916 | 917 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 918 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 919 | 920 | Object.defineProperties( THREE.OrbitControls.prototype, { 921 | 922 | center: { 923 | 924 | get: function () { 925 | 926 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 927 | return this.target; 928 | 929 | } 930 | 931 | }, 932 | 933 | // backward compatibility 934 | 935 | noZoom: { 936 | 937 | get: function () { 938 | 939 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 940 | return ! this.enableZoom; 941 | 942 | }, 943 | 944 | set: function ( value ) { 945 | 946 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 947 | this.enableZoom = ! value; 948 | 949 | } 950 | 951 | }, 952 | 953 | noRotate: { 954 | 955 | get: function () { 956 | 957 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 958 | return ! this.enableRotate; 959 | 960 | }, 961 | 962 | set: function ( value ) { 963 | 964 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 965 | this.enableRotate = ! value; 966 | 967 | } 968 | 969 | }, 970 | 971 | noPan: { 972 | 973 | get: function () { 974 | 975 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 976 | return ! this.enablePan; 977 | 978 | }, 979 | 980 | set: function ( value ) { 981 | 982 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 983 | this.enablePan = ! value; 984 | 985 | } 986 | 987 | }, 988 | 989 | noKeys: { 990 | 991 | get: function () { 992 | 993 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 994 | return ! this.enableKeys; 995 | 996 | }, 997 | 998 | set: function ( value ) { 999 | 1000 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1001 | this.enableKeys = ! value; 1002 | 1003 | } 1004 | 1005 | }, 1006 | 1007 | staticMoving: { 1008 | 1009 | get: function () { 1010 | 1011 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1012 | return ! this.enableDamping; 1013 | 1014 | }, 1015 | 1016 | set: function ( value ) { 1017 | 1018 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1019 | this.enableDamping = ! value; 1020 | 1021 | } 1022 | 1023 | }, 1024 | 1025 | dynamicDampingFactor: { 1026 | 1027 | get: function () { 1028 | 1029 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1030 | return this.dampingFactor; 1031 | 1032 | }, 1033 | 1034 | set: function ( value ) { 1035 | 1036 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1037 | this.dampingFactor = value; 1038 | 1039 | } 1040 | 1041 | } 1042 | 1043 | } ); 1044 | -------------------------------------------------------------------------------- /libs/three/OutlinePass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author spidersharma / http://eduperiment.com/ 3 | */ 4 | 5 | THREE.OutlinePass = function ( resolution, scene, camera, selectedObjects ) { 6 | 7 | this.renderScene = scene; 8 | this.renderCamera = camera; 9 | this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; 10 | this.visibleEdgeColor = new THREE.Color( 1, 1, 1 ); 11 | this.hiddenEdgeColor = new THREE.Color( 0.1, 0.04, 0.02 ); 12 | this.edgeGlow = 0.0; 13 | this.usePatternTexture = false; 14 | this.edgeThickness = 1.0; 15 | this.edgeStrength = 3.0; 16 | this.downSampleRatio = 2; 17 | this.pulsePeriod = 0; 18 | 19 | THREE.Pass.call( this ); 20 | 21 | this.resolution = ( resolution !== undefined ) ? new THREE.Vector2( resolution.x, resolution.y ) : new THREE.Vector2( 256, 256 ); 22 | 23 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }; 24 | 25 | var resx = Math.round( this.resolution.x / this.downSampleRatio ); 26 | var resy = Math.round( this.resolution.y / this.downSampleRatio ); 27 | 28 | this.maskBufferMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } ); 29 | this.maskBufferMaterial.side = THREE.DoubleSide; 30 | this.renderTargetMaskBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars ); 31 | this.renderTargetMaskBuffer.texture.name = "OutlinePass.mask"; 32 | this.renderTargetMaskBuffer.texture.generateMipmaps = false; 33 | 34 | this.depthMaterial = new THREE.MeshDepthMaterial(); 35 | this.depthMaterial.side = THREE.DoubleSide; 36 | this.depthMaterial.depthPacking = THREE.RGBADepthPacking; 37 | this.depthMaterial.blending = THREE.NoBlending; 38 | 39 | this.prepareMaskMaterial = this.getPrepareMaskMaterial(); 40 | this.prepareMaskMaterial.side = THREE.DoubleSide; 41 | this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); 42 | 43 | this.renderTargetDepthBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars ); 44 | this.renderTargetDepthBuffer.texture.name = "OutlinePass.depth"; 45 | this.renderTargetDepthBuffer.texture.generateMipmaps = false; 46 | 47 | this.renderTargetMaskDownSampleBuffer = new THREE.WebGLRenderTarget( resx, resy, pars ); 48 | this.renderTargetMaskDownSampleBuffer.texture.name = "OutlinePass.depthDownSample"; 49 | this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; 50 | 51 | this.renderTargetBlurBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars ); 52 | this.renderTargetBlurBuffer1.texture.name = "OutlinePass.blur1"; 53 | this.renderTargetBlurBuffer1.texture.generateMipmaps = false; 54 | this.renderTargetBlurBuffer2 = new THREE.WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), pars ); 55 | this.renderTargetBlurBuffer2.texture.name = "OutlinePass.blur2"; 56 | this.renderTargetBlurBuffer2.texture.generateMipmaps = false; 57 | 58 | this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); 59 | this.renderTargetEdgeBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars ); 60 | this.renderTargetEdgeBuffer1.texture.name = "OutlinePass.edge1"; 61 | this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; 62 | this.renderTargetEdgeBuffer2 = new THREE.WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), pars ); 63 | this.renderTargetEdgeBuffer2.texture.name = "OutlinePass.edge2"; 64 | this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; 65 | 66 | var MAX_EDGE_THICKNESS = 4; 67 | var MAX_EDGE_GLOW = 4; 68 | 69 | this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); 70 | this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 71 | this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = 1; 72 | this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); 73 | this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2( Math.round( resx / 2 ), Math.round( resy / 2 ) ); 74 | this.separableBlurMaterial2.uniforms[ "kernelRadius" ].value = MAX_EDGE_GLOW; 75 | 76 | // Overlay material 77 | this.overlayMaterial = this.getOverlayMaterial(); 78 | 79 | // copy material 80 | if ( THREE.CopyShader === undefined ) 81 | console.error( "THREE.OutlinePass relies on THREE.CopyShader" ); 82 | 83 | var copyShader = THREE.CopyShader; 84 | 85 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 86 | this.copyUniforms[ "opacity" ].value = 1.0; 87 | 88 | this.materialCopy = new THREE.ShaderMaterial( { 89 | uniforms: this.copyUniforms, 90 | vertexShader: copyShader.vertexShader, 91 | fragmentShader: copyShader.fragmentShader, 92 | blending: THREE.NoBlending, 93 | depthTest: false, 94 | depthWrite: false, 95 | transparent: true 96 | } ); 97 | 98 | this.enabled = true; 99 | this.needsSwap = false; 100 | 101 | this.oldClearColor = new THREE.Color(); 102 | this.oldClearAlpha = 1; 103 | 104 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 105 | this.scene = new THREE.Scene(); 106 | 107 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 108 | this.quad.frustumCulled = false; // Avoid getting clipped 109 | this.scene.add( this.quad ); 110 | 111 | this.tempPulseColor1 = new THREE.Color(); 112 | this.tempPulseColor2 = new THREE.Color(); 113 | this.textureMatrix = new THREE.Matrix4(); 114 | 115 | function replaceDepthToViewZ( string, camera ) { 116 | 117 | var type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; 118 | 119 | return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); 120 | 121 | } 122 | 123 | }; 124 | 125 | THREE.OutlinePass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 126 | 127 | constructor: THREE.OutlinePass, 128 | 129 | dispose: function () { 130 | 131 | this.renderTargetMaskBuffer.dispose(); 132 | this.renderTargetDepthBuffer.dispose(); 133 | this.renderTargetMaskDownSampleBuffer.dispose(); 134 | this.renderTargetBlurBuffer1.dispose(); 135 | this.renderTargetBlurBuffer2.dispose(); 136 | this.renderTargetEdgeBuffer1.dispose(); 137 | this.renderTargetEdgeBuffer2.dispose(); 138 | 139 | }, 140 | 141 | setSize: function ( width, height ) { 142 | 143 | this.renderTargetMaskBuffer.setSize( width, height ); 144 | 145 | var resx = Math.round( width / this.downSampleRatio ); 146 | var resy = Math.round( height / this.downSampleRatio ); 147 | this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); 148 | this.renderTargetBlurBuffer1.setSize( resx, resy ); 149 | this.renderTargetEdgeBuffer1.setSize( resx, resy ); 150 | this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 151 | 152 | resx = Math.round( resx / 2 ); 153 | resy = Math.round( resy / 2 ); 154 | 155 | this.renderTargetBlurBuffer2.setSize( resx, resy ); 156 | this.renderTargetEdgeBuffer2.setSize( resx, resy ); 157 | 158 | this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 159 | 160 | }, 161 | 162 | changeVisibilityOfSelectedObjects: function ( bVisible ) { 163 | 164 | function gatherSelectedMeshesCallBack( object ) { 165 | 166 | if ( object.isMesh ) { 167 | 168 | if ( bVisible ) { 169 | 170 | object.visible = object.userData.oldVisible; 171 | delete object.userData.oldVisible; 172 | 173 | } else { 174 | 175 | object.userData.oldVisible = object.visible; 176 | object.visible = bVisible; 177 | 178 | } 179 | 180 | } 181 | 182 | } 183 | 184 | for ( var i = 0; i < this.selectedObjects.length; i ++ ) { 185 | 186 | var selectedObject = this.selectedObjects[ i ]; 187 | selectedObject.traverse( gatherSelectedMeshesCallBack ); 188 | 189 | } 190 | 191 | }, 192 | 193 | changeVisibilityOfNonSelectedObjects: function ( bVisible ) { 194 | 195 | var selectedMeshes = []; 196 | 197 | function gatherSelectedMeshesCallBack( object ) { 198 | 199 | if ( object.isMesh ) selectedMeshes.push( object ); 200 | 201 | } 202 | 203 | for ( var i = 0; i < this.selectedObjects.length; i ++ ) { 204 | 205 | var selectedObject = this.selectedObjects[ i ]; 206 | selectedObject.traverse( gatherSelectedMeshesCallBack ); 207 | 208 | } 209 | 210 | function VisibilityChangeCallBack( object ) { 211 | 212 | if ( object.isMesh || object.isLine || object.isSprite ) { 213 | 214 | var bFound = false; 215 | 216 | for ( var i = 0; i < selectedMeshes.length; i ++ ) { 217 | 218 | var selectedObjectId = selectedMeshes[ i ].id; 219 | 220 | if ( selectedObjectId === object.id ) { 221 | 222 | bFound = true; 223 | break; 224 | 225 | } 226 | 227 | } 228 | 229 | if ( ! bFound ) { 230 | 231 | var visibility = object.visible; 232 | 233 | if ( ! bVisible || object.bVisible ) object.visible = bVisible; 234 | 235 | object.bVisible = visibility; 236 | 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | this.renderScene.traverse( VisibilityChangeCallBack ); 244 | 245 | }, 246 | 247 | updateTextureMatrix: function () { 248 | 249 | this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, 250 | 0.0, 0.5, 0.0, 0.5, 251 | 0.0, 0.0, 0.5, 0.5, 252 | 0.0, 0.0, 0.0, 1.0 ); 253 | this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); 254 | this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); 255 | 256 | }, 257 | 258 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 259 | 260 | if ( this.selectedObjects.length > 0 ) { 261 | 262 | this.oldClearColor.copy( renderer.getClearColor() ); 263 | this.oldClearAlpha = renderer.getClearAlpha(); 264 | var oldAutoClear = renderer.autoClear; 265 | 266 | renderer.autoClear = false; 267 | 268 | if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST ); 269 | 270 | renderer.setClearColor( 0xffffff, 1 ); 271 | 272 | // Make selected objects invisible 273 | this.changeVisibilityOfSelectedObjects( false ); 274 | 275 | var currentBackground = this.renderScene.background; 276 | this.renderScene.background = null; 277 | 278 | // 1. Draw Non Selected objects in the depth buffer 279 | this.renderScene.overrideMaterial = this.depthMaterial; 280 | renderer.render( this.renderScene, this.renderCamera, this.renderTargetDepthBuffer, true ); 281 | 282 | // Make selected objects visible 283 | this.changeVisibilityOfSelectedObjects( true ); 284 | 285 | // Update Texture Matrix for Depth compare 286 | this.updateTextureMatrix(); 287 | 288 | // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects 289 | this.changeVisibilityOfNonSelectedObjects( false ); 290 | this.renderScene.overrideMaterial = this.prepareMaskMaterial; 291 | this.prepareMaskMaterial.uniforms[ "cameraNearFar" ].value = new THREE.Vector2( this.renderCamera.near, this.renderCamera.far ); 292 | this.prepareMaskMaterial.uniforms[ "depthTexture" ].value = this.renderTargetDepthBuffer.texture; 293 | this.prepareMaskMaterial.uniforms[ "textureMatrix" ].value = this.textureMatrix; 294 | renderer.render( this.renderScene, this.renderCamera, this.renderTargetMaskBuffer, true ); 295 | this.renderScene.overrideMaterial = null; 296 | this.changeVisibilityOfNonSelectedObjects( true ); 297 | 298 | this.renderScene.background = currentBackground; 299 | 300 | // 2. Downsample to Half resolution 301 | this.quad.material = this.materialCopy; 302 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetMaskBuffer.texture; 303 | renderer.render( this.scene, this.camera, this.renderTargetMaskDownSampleBuffer, true ); 304 | 305 | this.tempPulseColor1.copy( this.visibleEdgeColor ); 306 | this.tempPulseColor2.copy( this.hiddenEdgeColor ); 307 | 308 | if ( this.pulsePeriod > 0 ) { 309 | 310 | var scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; 311 | this.tempPulseColor1.multiplyScalar( scalar ); 312 | this.tempPulseColor2.multiplyScalar( scalar ); 313 | 314 | } 315 | 316 | // 3. Apply Edge Detection Pass 317 | this.quad.material = this.edgeDetectionMaterial; 318 | this.edgeDetectionMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskDownSampleBuffer.texture; 319 | this.edgeDetectionMaterial.uniforms[ "texSize" ].value = new THREE.Vector2( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); 320 | this.edgeDetectionMaterial.uniforms[ "visibleEdgeColor" ].value = this.tempPulseColor1; 321 | this.edgeDetectionMaterial.uniforms[ "hiddenEdgeColor" ].value = this.tempPulseColor2; 322 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true ); 323 | 324 | // 4. Apply Blur on Half res 325 | this.quad.material = this.separableBlurMaterial1; 326 | this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture; 327 | this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX; 328 | this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = this.edgeThickness; 329 | renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer1, true ); 330 | this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer1.texture; 331 | this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY; 332 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true ); 333 | 334 | // Apply Blur on quarter res 335 | this.quad.material = this.separableBlurMaterial2; 336 | this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture; 337 | this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX; 338 | renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer2, true ); 339 | this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer2.texture; 340 | this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY; 341 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer2, true ); 342 | 343 | // Blend it additively over the input texture 344 | this.quad.material = this.overlayMaterial; 345 | this.overlayMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskBuffer.texture; 346 | this.overlayMaterial.uniforms[ "edgeTexture1" ].value = this.renderTargetEdgeBuffer1.texture; 347 | this.overlayMaterial.uniforms[ "edgeTexture2" ].value = this.renderTargetEdgeBuffer2.texture; 348 | this.overlayMaterial.uniforms[ "patternTexture" ].value = this.patternTexture; 349 | this.overlayMaterial.uniforms[ "edgeStrength" ].value = this.edgeStrength; 350 | this.overlayMaterial.uniforms[ "edgeGlow" ].value = this.edgeGlow; 351 | this.overlayMaterial.uniforms[ "usePatternTexture" ].value = this.usePatternTexture; 352 | 353 | 354 | if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST ); 355 | 356 | renderer.render( this.scene, this.camera, readBuffer, false ); 357 | 358 | renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); 359 | renderer.autoClear = oldAutoClear; 360 | 361 | } 362 | 363 | if ( this.renderToScreen ) { 364 | 365 | this.quad.material = this.materialCopy; 366 | this.copyUniforms[ "tDiffuse" ].value = readBuffer.texture; 367 | renderer.render( this.scene, this.camera ); 368 | 369 | } 370 | 371 | }, 372 | 373 | getPrepareMaskMaterial: function () { 374 | 375 | return new THREE.ShaderMaterial( { 376 | 377 | uniforms: { 378 | "depthTexture": { value: null }, 379 | "cameraNearFar": { value: new THREE.Vector2( 0.5, 0.5 ) }, 380 | "textureMatrix": { value: new THREE.Matrix4() } 381 | }, 382 | 383 | vertexShader: [ 384 | 'varying vec4 projTexCoord;', 385 | 'varying vec4 vPosition;', 386 | 'uniform mat4 textureMatrix;', 387 | 388 | 'void main() {', 389 | 390 | ' vPosition = modelViewMatrix * vec4( position, 1.0 );', 391 | ' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );', 392 | ' projTexCoord = textureMatrix * worldPosition;', 393 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 394 | 395 | '}' 396 | ].join( '\n' ), 397 | 398 | fragmentShader: [ 399 | '#include ', 400 | 'varying vec4 vPosition;', 401 | 'varying vec4 projTexCoord;', 402 | 'uniform sampler2D depthTexture;', 403 | 'uniform vec2 cameraNearFar;', 404 | 405 | 'void main() {', 406 | 407 | ' float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord ));', 408 | ' float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y );', 409 | ' float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0;', 410 | ' gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0);', 411 | 412 | '}' 413 | ].join( '\n' ) 414 | 415 | } ); 416 | 417 | }, 418 | 419 | getEdgeDetectionMaterial: function () { 420 | 421 | return new THREE.ShaderMaterial( { 422 | 423 | uniforms: { 424 | "maskTexture": { value: null }, 425 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 426 | "visibleEdgeColor": { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) }, 427 | "hiddenEdgeColor": { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) }, 428 | }, 429 | 430 | vertexShader: 431 | "varying vec2 vUv;\n\ 432 | void main() {\n\ 433 | vUv = uv;\n\ 434 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 435 | }", 436 | 437 | fragmentShader: 438 | "varying vec2 vUv;\ 439 | uniform sampler2D maskTexture;\ 440 | uniform vec2 texSize;\ 441 | uniform vec3 visibleEdgeColor;\ 442 | uniform vec3 hiddenEdgeColor;\ 443 | \ 444 | void main() {\n\ 445 | vec2 invSize = 1.0 / texSize;\ 446 | vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);\ 447 | vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);\ 448 | vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);\ 449 | vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);\ 450 | vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);\ 451 | float diff1 = (c1.r - c2.r)*0.5;\ 452 | float diff2 = (c3.r - c4.r)*0.5;\ 453 | float d = length( vec2(diff1, diff2) );\ 454 | float a1 = min(c1.g, c2.g);\ 455 | float a2 = min(c3.g, c4.g);\ 456 | float visibilityFactor = min(a1, a2);\ 457 | vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor;\ 458 | gl_FragColor = vec4(edgeColor, 1.0) * vec4(d);\ 459 | }" 460 | } ); 461 | 462 | }, 463 | 464 | getSeperableBlurMaterial: function ( maxRadius ) { 465 | 466 | return new THREE.ShaderMaterial( { 467 | 468 | defines: { 469 | "MAX_RADIUS": maxRadius, 470 | }, 471 | 472 | uniforms: { 473 | "colorTexture": { value: null }, 474 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 475 | "direction": { value: new THREE.Vector2( 0.5, 0.5 ) }, 476 | "kernelRadius": { value: 1.0 } 477 | }, 478 | 479 | vertexShader: 480 | "varying vec2 vUv;\n\ 481 | void main() {\n\ 482 | vUv = uv;\n\ 483 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 484 | }", 485 | 486 | fragmentShader: 487 | "#include \ 488 | varying vec2 vUv;\ 489 | uniform sampler2D colorTexture;\ 490 | uniform vec2 texSize;\ 491 | uniform vec2 direction;\ 492 | uniform float kernelRadius;\ 493 | \ 494 | float gaussianPdf(in float x, in float sigma) {\ 495 | return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\ 496 | }\ 497 | void main() {\ 498 | vec2 invSize = 1.0 / texSize;\ 499 | float weightSum = gaussianPdf(0.0, kernelRadius);\ 500 | vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\ 501 | vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);\ 502 | vec2 uvOffset = delta;\ 503 | for( int i = 1; i <= MAX_RADIUS; i ++ ) {\ 504 | float w = gaussianPdf(uvOffset.x, kernelRadius);\ 505 | vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\ 506 | vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\ 507 | diffuseSum += ((sample1 + sample2) * w);\ 508 | weightSum += (2.0 * w);\ 509 | uvOffset += delta;\ 510 | }\ 511 | gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\ 512 | }" 513 | } ); 514 | 515 | }, 516 | 517 | getOverlayMaterial: function () { 518 | 519 | return new THREE.ShaderMaterial( { 520 | 521 | uniforms: { 522 | "maskTexture": { value: null }, 523 | "edgeTexture1": { value: null }, 524 | "edgeTexture2": { value: null }, 525 | "patternTexture": { value: null }, 526 | "edgeStrength": { value: 1.0 }, 527 | "edgeGlow": { value: 1.0 }, 528 | "usePatternTexture": { value: 0.0 } 529 | }, 530 | 531 | vertexShader: 532 | "varying vec2 vUv;\n\ 533 | void main() {\n\ 534 | vUv = uv;\n\ 535 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 536 | }", 537 | 538 | fragmentShader: 539 | "varying vec2 vUv;\ 540 | uniform sampler2D maskTexture;\ 541 | uniform sampler2D edgeTexture1;\ 542 | uniform sampler2D edgeTexture2;\ 543 | uniform sampler2D patternTexture;\ 544 | uniform float edgeStrength;\ 545 | uniform float edgeGlow;\ 546 | uniform bool usePatternTexture;\ 547 | \ 548 | void main() {\ 549 | vec4 edgeValue1 = texture2D(edgeTexture1, vUv);\ 550 | vec4 edgeValue2 = texture2D(edgeTexture2, vUv);\ 551 | vec4 maskColor = texture2D(maskTexture, vUv);\ 552 | vec4 patternColor = texture2D(patternTexture, 6.0 * vUv);\ 553 | float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5;\ 554 | vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow;\ 555 | vec4 finalColor = edgeStrength * maskColor.r * edgeValue;\ 556 | if(usePatternTexture)\ 557 | finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r);\ 558 | gl_FragColor = finalColor;\ 559 | }", 560 | blending: THREE.AdditiveBlending, 561 | depthTest: false, 562 | depthWrite: false, 563 | transparent: true 564 | } ); 565 | 566 | } 567 | 568 | } ); 569 | 570 | THREE.OutlinePass.BlurDirectionX = new THREE.Vector2( 1.0, 0.0 ); 571 | THREE.OutlinePass.BlurDirectionY = new THREE.Vector2( 0.0, 1.0 ); 572 | -------------------------------------------------------------------------------- /libs/three/Reflector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Slayvin / http://slayvin.net 3 | */ 4 | 5 | THREE.Reflector = function ( geometry, options ) { 6 | 7 | THREE.Mesh.call( this, geometry ); 8 | 9 | this.type = 'Reflector'; 10 | 11 | var scope = this; 12 | 13 | options = options || {}; 14 | 15 | var color = ( options.color !== undefined ) ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F ); 16 | var textureWidth = options.textureWidth || 512; 17 | var textureHeight = options.textureHeight || 512; 18 | var clipBias = options.clipBias || 0; 19 | var shader = options.shader || THREE.Reflector.ReflectorShader; 20 | var recursion = options.recursion !== undefined ? options.recursion : 0; 21 | 22 | // 23 | 24 | var reflectorPlane = new THREE.Plane(); 25 | var normal = new THREE.Vector3(); 26 | var reflectorWorldPosition = new THREE.Vector3(); 27 | var cameraWorldPosition = new THREE.Vector3(); 28 | var rotationMatrix = new THREE.Matrix4(); 29 | var lookAtPosition = new THREE.Vector3( 0, 0, - 1 ); 30 | var clipPlane = new THREE.Vector4(); 31 | var viewport = new THREE.Vector4(); 32 | 33 | var view = new THREE.Vector3(); 34 | var target = new THREE.Vector3(); 35 | var q = new THREE.Vector4(); 36 | 37 | var textureMatrix = new THREE.Matrix4(); 38 | var virtualCamera = new THREE.PerspectiveCamera(); 39 | 40 | var parameters = { 41 | minFilter: THREE.LinearFilter, 42 | magFilter: THREE.LinearFilter, 43 | format: THREE.RGBFormat, 44 | stencilBuffer: false 45 | }; 46 | 47 | var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); 48 | 49 | if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || ! THREE.Math.isPowerOfTwo( textureHeight ) ) { 50 | 51 | renderTarget.texture.generateMipmaps = false; 52 | 53 | } 54 | 55 | var material = new THREE.ShaderMaterial( { 56 | uniforms: THREE.UniformsUtils.clone( shader.uniforms ), 57 | fragmentShader: shader.fragmentShader, 58 | vertexShader: shader.vertexShader, 59 | 60 | } ); 61 | 62 | material.uniforms.tDiffuse.value = renderTarget.texture; 63 | material.uniforms.color.value = color; 64 | material.uniforms.textureMatrix.value = textureMatrix; 65 | 66 | this.material = material; 67 | 68 | this.onBeforeRender = function ( renderer, scene, camera ) { 69 | 70 | if ( 'recursion' in camera.userData ) { 71 | 72 | if ( camera.userData.recursion === recursion ) return; 73 | 74 | camera.userData.recursion ++; 75 | 76 | } 77 | 78 | reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); 79 | cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); 80 | 81 | rotationMatrix.extractRotation( scope.matrixWorld ); 82 | 83 | normal.set( 0, 0, 1 ); 84 | normal.applyMatrix4( rotationMatrix ); 85 | 86 | view.subVectors( reflectorWorldPosition, cameraWorldPosition ); 87 | 88 | // Avoid rendering when reflector is facing away 89 | 90 | if ( view.dot( normal ) > 0 ) return; 91 | 92 | view.reflect( normal ).negate(); 93 | view.add( reflectorWorldPosition ); 94 | 95 | rotationMatrix.extractRotation( camera.matrixWorld ); 96 | 97 | lookAtPosition.set( 0, 0, - 1 ); 98 | lookAtPosition.applyMatrix4( rotationMatrix ); 99 | lookAtPosition.add( cameraWorldPosition ); 100 | 101 | target.subVectors( reflectorWorldPosition, lookAtPosition ); 102 | target.reflect( normal ).negate(); 103 | target.add( reflectorWorldPosition ); 104 | 105 | virtualCamera.position.copy( view ); 106 | virtualCamera.up.set( 0, 1, 0 ); 107 | virtualCamera.up.applyMatrix4( rotationMatrix ); 108 | virtualCamera.up.reflect( normal ); 109 | virtualCamera.lookAt( target ); 110 | 111 | virtualCamera.far = camera.far; // Used in WebGLBackground 112 | 113 | virtualCamera.updateMatrixWorld(); 114 | virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); 115 | 116 | virtualCamera.userData.recursion = 0; 117 | 118 | // Update the texture matrix 119 | textureMatrix.set( 120 | 0.5, 0.0, 0.0, 0.5, 121 | 0.0, 0.5, 0.0, 0.5, 122 | 0.0, 0.0, 0.5, 0.5, 123 | 0.0, 0.0, 0.0, 1.0 124 | ); 125 | textureMatrix.multiply( virtualCamera.projectionMatrix ); 126 | textureMatrix.multiply( virtualCamera.matrixWorldInverse ); 127 | textureMatrix.multiply( scope.matrixWorld ); 128 | 129 | // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html 130 | // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf 131 | reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); 132 | reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); 133 | 134 | clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); 135 | 136 | var projectionMatrix = virtualCamera.projectionMatrix; 137 | 138 | q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; 139 | q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; 140 | q.z = - 1.0; 141 | q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; 142 | 143 | // Calculate the scaled plane vector 144 | clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); 145 | 146 | // Replacing the third row of the projection matrix 147 | projectionMatrix.elements[ 2 ] = clipPlane.x; 148 | projectionMatrix.elements[ 6 ] = clipPlane.y; 149 | projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; 150 | projectionMatrix.elements[ 14 ] = clipPlane.w; 151 | 152 | // Render 153 | 154 | scope.visible = false; 155 | 156 | var currentRenderTarget = renderer.getRenderTarget(); 157 | 158 | var currentVrEnabled = renderer.vr.enabled; 159 | var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; 160 | 161 | renderer.vr.enabled = false; // Avoid camera modification and recursion 162 | renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows 163 | 164 | renderer.render( scene, virtualCamera, renderTarget, true ); 165 | 166 | renderer.vr.enabled = currentVrEnabled; 167 | renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; 168 | 169 | renderer.setRenderTarget( currentRenderTarget ); 170 | 171 | // Restore viewport 172 | 173 | var bounds = camera.bounds; 174 | 175 | if ( bounds !== undefined ) { 176 | 177 | var size = renderer.getSize(); 178 | var pixelRatio = renderer.getPixelRatio(); 179 | 180 | viewport.x = bounds.x * size.width * pixelRatio; 181 | viewport.y = bounds.y * size.height * pixelRatio; 182 | viewport.z = bounds.z * size.width * pixelRatio; 183 | viewport.w = bounds.w * size.height * pixelRatio; 184 | 185 | renderer.state.viewport( viewport ); 186 | 187 | } 188 | 189 | scope.visible = true; 190 | 191 | }; 192 | 193 | this.getRenderTarget = function () { 194 | 195 | return renderTarget; 196 | 197 | }; 198 | 199 | }; 200 | 201 | THREE.Reflector.prototype = Object.create( THREE.Mesh.prototype ); 202 | THREE.Reflector.prototype.constructor = THREE.Reflector; 203 | 204 | THREE.Reflector.ReflectorShader = { 205 | 206 | uniforms: { 207 | 208 | 'color': { 209 | type: 'c', 210 | value: null 211 | }, 212 | 213 | 'tDiffuse': { 214 | type: 't', 215 | value: null 216 | }, 217 | 218 | 'textureMatrix': { 219 | type: 'm4', 220 | value: null 221 | } 222 | 223 | }, 224 | 225 | vertexShader: [ 226 | 'uniform mat4 textureMatrix;', 227 | 'varying vec4 vUv;', 228 | 229 | 'void main() {', 230 | 231 | ' vUv = textureMatrix * vec4( position, 1.0 );', 232 | 233 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 234 | 235 | '}' 236 | ].join( '\n' ), 237 | 238 | fragmentShader: [ 239 | 'uniform vec3 color;', 240 | 'uniform sampler2D tDiffuse;', 241 | 'varying vec4 vUv;', 242 | 243 | 'float blendOverlay( float base, float blend ) {', 244 | 245 | ' return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );', 246 | 247 | '}', 248 | 249 | 'vec3 blendOverlay( vec3 base, vec3 blend ) {', 250 | 251 | ' return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );', 252 | 253 | '}', 254 | 255 | 'void main() {', 256 | 257 | ' vec4 base = texture2DProj( tDiffuse, vUv );', 258 | ' gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );', 259 | 260 | '}' 261 | ].join( '\n' ) 262 | }; 263 | -------------------------------------------------------------------------------- /libs/three/RenderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { 6 | 7 | THREE.Pass.call( this ); 8 | 9 | this.scene = scene; 10 | this.camera = camera; 11 | 12 | this.overrideMaterial = overrideMaterial; 13 | 14 | this.clearColor = clearColor; 15 | this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; 16 | 17 | this.clear = true; 18 | this.clearDepth = false; 19 | this.needsSwap = false; 20 | 21 | }; 22 | 23 | THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 24 | 25 | constructor: THREE.RenderPass, 26 | 27 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 28 | 29 | var oldAutoClear = renderer.autoClear; 30 | renderer.autoClear = false; 31 | 32 | this.scene.overrideMaterial = this.overrideMaterial; 33 | 34 | var oldClearColor, oldClearAlpha; 35 | 36 | if ( this.clearColor ) { 37 | 38 | oldClearColor = renderer.getClearColor().getHex(); 39 | oldClearAlpha = renderer.getClearAlpha(); 40 | 41 | renderer.setClearColor( this.clearColor, this.clearAlpha ); 42 | 43 | } 44 | 45 | if ( this.clearDepth ) { 46 | 47 | renderer.clearDepth(); 48 | 49 | } 50 | 51 | renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear ); 52 | 53 | if ( this.clearColor ) { 54 | 55 | renderer.setClearColor( oldClearColor, oldClearAlpha ); 56 | 57 | } 58 | 59 | this.scene.overrideMaterial = null; 60 | renderer.autoClear = oldAutoClear; 61 | } 62 | 63 | } ); 64 | -------------------------------------------------------------------------------- /libs/three/ShaderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.ShaderPass = function ( shader, textureID ) { 6 | 7 | THREE.Pass.call( this ); 8 | 9 | this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; 10 | 11 | if ( shader instanceof THREE.ShaderMaterial ) { 12 | 13 | this.uniforms = shader.uniforms; 14 | 15 | this.material = shader; 16 | 17 | } else if ( shader ) { 18 | 19 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 20 | 21 | this.material = new THREE.ShaderMaterial( { 22 | 23 | defines: Object.assign( {}, shader.defines ), 24 | uniforms: this.uniforms, 25 | vertexShader: shader.vertexShader, 26 | fragmentShader: shader.fragmentShader 27 | 28 | } ); 29 | 30 | } 31 | 32 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 33 | this.scene = new THREE.Scene(); 34 | 35 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 36 | this.quad.frustumCulled = false; // Avoid getting clipped 37 | this.scene.add( this.quad ); 38 | 39 | }; 40 | 41 | THREE.ShaderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 42 | 43 | constructor: THREE.ShaderPass, 44 | 45 | render: function( renderer, writeBuffer, readBuffer, delta, maskActive ) { 46 | 47 | if ( this.uniforms[ this.textureID ] ) { 48 | 49 | this.uniforms[ this.textureID ].value = readBuffer.texture; 50 | 51 | } 52 | 53 | this.quad.material = this.material; 54 | 55 | if ( this.renderToScreen ) { 56 | 57 | renderer.render( this.scene, this.camera ); 58 | 59 | } else { 60 | 61 | renderer.render( this.scene, this.camera, writeBuffer, this.clear ); 62 | 63 | } 64 | 65 | } 66 | 67 | } ); 68 | -------------------------------------------------------------------------------- /libs/three/THREE.MeshLine.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | 3 | "use strict"; 4 | 5 | var root = this 6 | 7 | var has_require = typeof require !== 'undefined' 8 | 9 | var THREE = root.THREE || has_require && require('three') 10 | if( !THREE ) 11 | throw new Error( 'MeshLine requires three.js' ) 12 | 13 | function MeshLine() { 14 | 15 | this.positions = []; 16 | 17 | this.previous = []; 18 | this.next = []; 19 | this.side = []; 20 | this.width = []; 21 | this.indices_array = []; 22 | this.uvs = []; 23 | this.counters = []; 24 | this.geometry = new THREE.BufferGeometry(); 25 | 26 | this.widthCallback = null; 27 | 28 | } 29 | 30 | MeshLine.prototype.setGeometry = function( g, c ) { 31 | 32 | this.widthCallback = c; 33 | 34 | this.positions = []; 35 | this.counters = []; 36 | 37 | if( g instanceof THREE.Geometry ) { 38 | for( var j = 0; j < g.vertices.length; j++ ) { 39 | var v = g.vertices[ j ]; 40 | var c = j/g.vertices.length; 41 | this.positions.push( v.x, v.y, v.z ); 42 | this.positions.push( v.x, v.y, v.z ); 43 | this.counters.push(c); 44 | this.counters.push(c); 45 | } 46 | } 47 | 48 | if( g instanceof THREE.BufferGeometry ) { 49 | // read attribute positions ? 50 | } 51 | 52 | if( g instanceof Float32Array || g instanceof Array ) { 53 | for( var j = 0; j < g.length; j += 3 ) { 54 | var c = j/g.length; 55 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 56 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 57 | this.counters.push(c); 58 | this.counters.push(c); 59 | } 60 | } 61 | 62 | this.process(); 63 | 64 | } 65 | 66 | MeshLine.prototype.compareV3 = function( a, b ) { 67 | 68 | var aa = a * 6; 69 | var ab = b * 6; 70 | return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] ); 71 | 72 | } 73 | 74 | MeshLine.prototype.copyV3 = function( a ) { 75 | 76 | var aa = a * 6; 77 | return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ]; 78 | 79 | } 80 | 81 | MeshLine.prototype.process = function() { 82 | 83 | var l = this.positions.length / 6; 84 | 85 | this.previous = []; 86 | this.next = []; 87 | this.side = []; 88 | this.width = []; 89 | this.indices_array = []; 90 | this.uvs = []; 91 | 92 | for( var j = 0; j < l; j++ ) { 93 | this.side.push( 1 ); 94 | this.side.push( -1 ); 95 | } 96 | 97 | var w; 98 | for( var j = 0; j < l; j++ ) { 99 | if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) ); 100 | else w = 1; 101 | this.width.push( w ); 102 | this.width.push( w ); 103 | } 104 | 105 | for( var j = 0; j < l; j++ ) { 106 | this.uvs.push( j / ( l - 1 ), 0 ); 107 | this.uvs.push( j / ( l - 1 ), 1 ); 108 | } 109 | 110 | var v; 111 | 112 | if( this.compareV3( 0, l - 1 ) ){ 113 | v = this.copyV3( l - 2 ); 114 | } else { 115 | v = this.copyV3( 0 ); 116 | } 117 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 118 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 119 | for( var j = 0; j < l - 1; j++ ) { 120 | v = this.copyV3( j ); 121 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 122 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 123 | } 124 | 125 | for( var j = 1; j < l; j++ ) { 126 | v = this.copyV3( j ); 127 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 128 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 129 | } 130 | 131 | if( this.compareV3( l - 1, 0 ) ){ 132 | v = this.copyV3( 1 ); 133 | } else { 134 | v = this.copyV3( l - 1 ); 135 | } 136 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 137 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 138 | 139 | for( var j = 0; j < l - 1; j++ ) { 140 | var n = j * 2; 141 | this.indices_array.push( n, n + 1, n + 2 ); 142 | this.indices_array.push( n + 2, n + 1, n + 3 ); 143 | } 144 | 145 | if (!this.attributes) { 146 | this.attributes = { 147 | position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ), 148 | previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ), 149 | next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ), 150 | side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ), 151 | width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ), 152 | uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ), 153 | index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ), 154 | counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 ) 155 | } 156 | } else { 157 | this.attributes.position.copyArray(new Float32Array(this.positions)); 158 | this.attributes.position.needsUpdate = true; 159 | this.attributes.previous.copyArray(new Float32Array(this.previous)); 160 | this.attributes.previous.needsUpdate = true; 161 | this.attributes.next.copyArray(new Float32Array(this.next)); 162 | this.attributes.next.needsUpdate = true; 163 | this.attributes.side.copyArray(new Float32Array(this.side)); 164 | this.attributes.side.needsUpdate = true; 165 | this.attributes.width.copyArray(new Float32Array(this.width)); 166 | this.attributes.width.needsUpdate = true; 167 | this.attributes.uv.copyArray(new Float32Array(this.uvs)); 168 | this.attributes.uv.needsUpdate = true; 169 | this.attributes.index.copyArray(new Uint16Array(this.indices_array)); 170 | this.attributes.index.needsUpdate = true; 171 | } 172 | 173 | this.geometry.addAttribute( 'position', this.attributes.position ); 174 | this.geometry.addAttribute( 'previous', this.attributes.previous ); 175 | this.geometry.addAttribute( 'next', this.attributes.next ); 176 | this.geometry.addAttribute( 'side', this.attributes.side ); 177 | this.geometry.addAttribute( 'width', this.attributes.width ); 178 | this.geometry.addAttribute( 'uv', this.attributes.uv ); 179 | this.geometry.addAttribute( 'counters', this.attributes.counters ); 180 | 181 | this.geometry.setIndex( this.attributes.index ); 182 | 183 | } 184 | 185 | function memcpy (src, srcOffset, dst, dstOffset, length) { 186 | var i 187 | 188 | src = src.subarray || src.slice ? src : src.buffer 189 | dst = dst.subarray || dst.slice ? dst : dst.buffer 190 | 191 | src = srcOffset ? src.subarray ? 192 | src.subarray(srcOffset, length && srcOffset + length) : 193 | src.slice(srcOffset, length && srcOffset + length) : src 194 | 195 | if (dst.set) { 196 | dst.set(src, dstOffset) 197 | } else { 198 | for (i=0; in;n++)E[n].stop()},this.delay=function(n){return s=n,this},this.repeat=function(n){return e=n,this},this.yoyo=function(n){return a=n,this},this.easing=function(n){return l=n,this},this.interpolation=function(n){return p=n,this},this.chain=function(){return E=arguments,this},this.onStart=function(n){return d=n,this},this.onUpdate=function(n){return I=n,this},this.onComplete=function(n){return w=n,this},this.onStop=function(n){return M=n,this},this.update=function(n){var f;if(h>n)return!0;v===!1&&(null!==d&&d.call(t),v=!0);var M=(n-h)/o;M=M>1?1:M;var O=l(M);for(f in i){var m=r[f]||0,N=i[f];N instanceof Array?t[f]=p(N,O):("string"==typeof N&&(N=m+parseFloat(N,10)),"number"==typeof N&&(t[f]=m+(N-m)*O))}if(null!==I&&I.call(t,O),1==M){if(e>0){isFinite(e)&&e--;for(f in u){if("string"==typeof i[f]&&(u[f]=u[f]+parseFloat(i[f],10)),a){var T=u[f];u[f]=i[f],i[f]=T}r[f]=u[f]}return a&&(c=!c),h=n+s,!0}null!==w&&w.call(t);for(var g=0,W=E.length;W>g;g++)E[g].start(n);return!1}return!0}},TWEEN.Easing={Linear:{None:function(n){return n}},Quadratic:{In:function(n){return n*n},Out:function(n){return n*(2-n)},InOut:function(n){return(n*=2)<1?.5*n*n:-.5*(--n*(n-2)-1)}},Cubic:{In:function(n){return n*n*n},Out:function(n){return--n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n:.5*((n-=2)*n*n+2)}},Quartic:{In:function(n){return n*n*n*n},Out:function(n){return 1- --n*n*n*n},InOut:function(n){return(n*=2)<1?.5*n*n*n*n:-.5*((n-=2)*n*n*n-2)}},Quintic:{In:function(n){return n*n*n*n*n},Out:function(n){return--n*n*n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n*n*n:.5*((n-=2)*n*n*n*n+2)}},Sinusoidal:{In:function(n){return 1-Math.cos(n*Math.PI/2)},Out:function(n){return Math.sin(n*Math.PI/2)},InOut:function(n){return.5*(1-Math.cos(Math.PI*n))}},Exponential:{In:function(n){return 0===n?0:Math.pow(1024,n-1)},Out:function(n){return 1===n?1:1-Math.pow(2,-10*n)},InOut:function(n){return 0===n?0:1===n?1:(n*=2)<1?.5*Math.pow(1024,n-1):.5*(-Math.pow(2,-10*(n-1))+2)}},Circular:{In:function(n){return 1-Math.sqrt(1-n*n)},Out:function(n){return Math.sqrt(1- --n*n)},InOut:function(n){return(n*=2)<1?-.5*(Math.sqrt(1-n*n)-1):.5*(Math.sqrt(1-(n-=2)*n)+1)}},Elastic:{In:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),-(r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)))},Out:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),r*Math.pow(2,-10*n)*Math.sin(2*(n-t)*Math.PI/i)+1)},InOut:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),(n*=2)<1?-.5*r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i):r*Math.pow(2,-10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)*.5+1)}},Back:{In:function(n){var t=1.70158;return n*n*((t+1)*n-t)},Out:function(n){var t=1.70158;return--n*n*((t+1)*n+t)+1},InOut:function(n){var t=2.5949095;return(n*=2)<1?.5*n*n*((t+1)*n-t):.5*((n-=2)*n*((t+1)*n+t)+2)}},Bounce:{In:function(n){return 1-TWEEN.Easing.Bounce.Out(1-n)},Out:function(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375},InOut:function(n){return.5>n?.5*TWEEN.Easing.Bounce.In(2*n):.5*TWEEN.Easing.Bounce.Out(2*n-1)+.5}}},TWEEN.Interpolation={Linear:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.Linear;return 0>t?o(n[0],n[1],i):t>1?o(n[r],n[r-1],r-i):o(n[u],n[u+1>r?r:u+1],i-u)},Bezier:function(n,t){var r,i=0,u=n.length-1,o=Math.pow,e=TWEEN.Interpolation.Utils.Bernstein;for(r=0;u>=r;r++)i+=o(1-t,u-r)*o(t,r)*n[r]*e(u,r);return i},CatmullRom:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.CatmullRom;return n[0]===n[r]?(0>t&&(u=Math.floor(i=r*(1+t))),o(n[(u-1+r)%r],n[u],n[(u+1)%r],n[(u+2)%r],i-u)):0>t?n[0]-(o(n[0],n[0],n[1],n[1],-i)-n[0]):t>1?n[r]-(o(n[r],n[r],n[r-1],n[r-1],i-r)-n[r]):o(n[u?u-1:0],n[u],n[u+1>r?r:u+1],n[u+2>r?r:u+2],i-u)},Utils:{Linear:function(n,t,r){return(t-n)*r+n},Bernstein:function(n,t){var r=TWEEN.Interpolation.Utils.Factorial;return r(n)/r(t)/r(n-t)},Factorial:function(){var n=[1];return function(t){var r,i=1;if(n[t])return n[t];for(r=t;r>1;r--)i*=r;return n[t]=i}}(),CatmullRom:function(n,t,r,i,u){var o=.5*(r-n),e=.5*(i-t),a=u*u,f=u*a;return(2*t-2*r+o+e)*f+(-3*t+3*r-2*o-e)*a+o*u+t}}},"undefined"!=typeof module&&module.exports&&(module.exports=TWEEN); --------------------------------------------------------------------------------