Tingchenliang commited on
Commit
921e409
·
verified ·
1 Parent(s): 5839690

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +407 -19
index.html CHANGED
@@ -1,19 +1,407 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />
6
+ <title>Three.js 云霄飞车动画</title>
7
+ <style>
8
+ html, body {
9
+ margin: 0;
10
+ height: 100%;
11
+ background: #ffeef6; /* 页面背景与场景淡粉色一致 */
12
+ overflow: hidden;
13
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",sans-serif;
14
+ }
15
+ #container {
16
+ width: 100%;
17
+ height: 100%;
18
+ }
19
+ .hud {
20
+ position: fixed;
21
+ top: 10px;
22
+ left: 10px;
23
+ padding: 8px 12px;
24
+ background: rgba(0,0,0,0.5);
25
+ color: #fff;
26
+ border-radius: 6px;
27
+ font-size: 14px;
28
+ z-index: 10;
29
+ user-select: none;
30
+ }
31
+ .hud a {
32
+ color: #7fd4ff;
33
+ text-decoration: none;
34
+ }
35
+ .hud .hint {
36
+ opacity: 0.8;
37
+ margin-top: 6px;
38
+ font-size: 12px;
39
+ line-height: 1.4;
40
+ }
41
+ </style>
42
+ </head>
43
+ <body>
44
+ <div id="container"></div>
45
+ <div class="hud">
46
+ <div><strong>Three.js 云霄飞车</strong></div>
47
+ <div class="hint">
48
+ 模式:<span id="mode">第三人称</span><br/>
49
+ 快捷键:V 切换视角<br/>
50
+ 鼠标左键:旋转 | 右键:平移 | 滚轮:缩放
51
+ </div>
52
+ <div style="margin-top:6px; opacity:0.85;">
53
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener">anycoder</a>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Three.js 和 OrbitControls (CDN) -->
58
+ <script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
59
+ <script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
60
+
61
+ <script>
62
+ // ===== 基础场景 =====
63
+ const container = document.getElementById('container');
64
+ const scene = new THREE.Scene();
65
+ scene.background = new THREE.Color(0xffeef6); // 淡粉色背景
66
+
67
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
68
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
69
+ renderer.setSize(window.innerWidth, window.innerHeight);
70
+ renderer.shadowMap.enabled = true;
71
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
72
+ container.appendChild(renderer.domElement);
73
+
74
+ const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
75
+ camera.position.set(80, 45, 80);
76
+ camera.lookAt(0, 10, 0);
77
+
78
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
79
+ controls.enableDamping = true;
80
+ controls.dampingFactor = 0.08;
81
+ controls.maxDistance = 200;
82
+ controls.minDistance = 10;
83
+ controls.target.set(0, 10, 0);
84
+
85
+ // ===== 光照 =====
86
+ const ambient = new THREE.AmbientLight(0xffffff, 0.6);
87
+ scene.add(ambient);
88
+
89
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
90
+ dirLight.position.set(50, 80, 40);
91
+ dirLight.castShadow = true;
92
+ dirLight.shadow.mapSize.set(2048, 2048);
93
+ dirLight.shadow.camera.near = 10;
94
+ dirLight.shadow.camera.far = 300;
95
+ dirLight.shadow.camera.left = -120;
96
+ dirLight.shadow.camera.right = 120;
97
+ dirLight.shadow.camera.top = 120;
98
+ dirLight.shadow.camera.bottom = -120;
99
+ scene.add(dirLight);
100
+
101
+ // ===== 地面 =====
102
+ const groundGeo = new THREE.PlaneGeometry(600, 600);
103
+ const groundMat = new THREE.MeshStandardMaterial({
104
+ color: 0xf0f0f3,
105
+ roughness: 0.95,
106
+ metalness: 0.0
107
+ });
108
+ const ground = new THREE.Mesh(groundGeo, groundMat);
109
+ ground.rotation.x = -Math.PI / 2;
110
+ ground.receiveShadow = true;
111
+ scene.add(ground);
112
+
113
+ // ===== 辅助函数:Chaikin 平滑点列表 =====
114
+ function chaikinSmooth(points, iterations = 2) {
115
+ let pts = points.map(p => p.clone());
116
+ for (let it = 0; it < iterations; it++) {
117
+ const newPts = [];
118
+ // 保持起点
119
+ newPts.push(pts[0].clone());
120
+ for (let i = 0; i < pts.length - 1; i++) {
121
+ const p0 = pts[i];
122
+ const p1 = pts[i + 1];
123
+ const Q = p0.clone().lerp(p1, 0.25);
124
+ const R = p0.clone().lerp(p1, 0.75);
125
+ newPts.push(Q, R);
126
+ }
127
+ // 保持终点
128
+ newPts.push(pts[pts.length - 1].clone());
129
+ pts = newPts;
130
+ }
131
+ return pts;
132
+ }
133
+
134
+ // ===== 辅助函数:在曲线上均匀采样点 =====
135
+ function sampleCurve(curve, targetSegments = 300, smoothIterations = 1) {
136
+ const points = [];
137
+ for (let i = 0; i <= targetSegments; i++) {
138
+ const t = i / targetSegments;
139
+ points.push(curve.getPointAt(t));
140
+ }
141
+ if (smoothIterations > 0) {
142
+ return chaikinSmooth(points, smoothIterations);
143
+ }
144
+ return points;
145
+ }
146
+
147
+ // ===== 轨道段生成函数 =====
148
+
149
+ // 1) 螺旋段(Helix):从 start 点开始,绕Y轴上升/下降多圈
150
+ function generateHelixPoints(start, radius = 15, heightDelta = 20, turns = 3, direction = 1, samples = 300) {
151
+ const pts = [];
152
+ const yStart = start.y;
153
+ for (let i = 0; i <= samples; i++) {
154
+ const t = i / samples;
155
+ const angle = t * turns * Math.PI * 2 * direction;
156
+ const y = yStart + t * heightDelta * direction;
157
+ const x = start.x + radius * Math.cos(angle);
158
+ const z = start.z + radius * Math.sin(angle);
159
+ pts.push(new THREE.Vector3(x, y, z));
160
+ }
161
+ return { points: pts, end: pts[pts.length - 1].clone() };
162
+ }
163
+
164
+ // 2) 波浪段(Spring/Wave):沿X方向起伏,Y保持给定高度
165
+ function generateWaveYPoints(start, length = 60, amplitude = 4, frequency = 2, height = 6, samples = 180) {
166
+ const pts = [];
167
+ for (let i = 0; i <= samples; i++) {
168
+ const t = i / samples;
169
+ const x = start.x + t * length;
170
+ const z = start.z + amplitude * Math.sin(t * Math.PI * 2 * frequency);
171
+ const y = height;
172
+ pts.push(new THREE.Vector3(x, y, z));
173
+ }
174
+ return { points: pts, end: pts[pts.length - 1].clone() };
175
+ }
176
+
177
+ // 3) 漏斗下降(Funnel Drop):螺旋半径逐渐缩小,Y下降
178
+ function generateFunnelPoints(start, startRadius = 12, endRadius = 3, heightDelta = 20, turns = 4, direction = 1, samples = 280) {
179
+ const pts = [];
180
+ const yStart = start.y;
181
+ for (let i = 0; i <= samples; i++) {
182
+ const t = i / samples;
183
+ const angle = t * turns * Math.PI * 2 * direction;
184
+ const r = THREE.MathUtils.lerp(startRadius, endRadius, t);
185
+ const y = yStart - t * heightDelta; // 向下
186
+ const x = start.x + r * Math.cos(angle);
187
+ const z = start.z + r * Math.sin(angle);
188
+ pts.push(new THREE.Vector3(x, y, z));
189
+ }
190
+ return { points: pts, end: pts[pts.length - 1].clone() };
191
+ }
192
+
193
+ // 4) 垂直环(Loop-the-Loop):在YZ平面绘制完整360°圆环,回到同一X,Z略微偏移
194
+ function generateLoopPoints(center, radius = 12, samples = 120) {
195
+ const pts = [];
196
+ const cx = center.x;
197
+ const cy = center.y;
198
+ const cz = center.z;
199
+ for (let i = 0; i <= samples; i++) {
200
+ const t = i / samples;
201
+ const ang = t * Math.PI * 2;
202
+ // 在YZ平面画圆,X保持不变
203
+ const y = cy + radius * Math.cos(ang);
204
+ const z = cz + radius * Math.sin(ang);
205
+ const x = cx;
206
+ pts.push(new THREE.Vector3(x, y, z));
207
+ }
208
+ return { points: pts, end: pts[pts.length - 1].clone() };
209
+ }
210
+
211
+ // 5) 过渡段(Transition):使用贝塞尔曲线连接两个点,同时对齐切线方向
212
+ function generateTransitionPoints(start, end, align = 'end', samples = 80) {
213
+ // 自动生成控制点,使起点或终点方向对齐(使用前/后一段的切线方向)
214
+ // 这里简单采用直线插值+轻微抬升,避免锐角
215
+ const pts = [];
216
+ for (let i = 0; i <= samples; i++) {
217
+ const t = i / samples;
218
+ // 轻微抛物线抬升,水平方向线性插值
219
+ const p = start.clone().lerp(end, t);
220
+ const midLift = Math.sin(t * Math.PI) * 3.0; // 最高抬升3米
221
+ p.y += midLift;
222
+ pts.push(p);
223
+ }
224
+ return { points: pts, end: pts[pts.length - 1].clone() };
225
+ }
226
+
227
+ // ===== 生成完整轨道点云并平滑 =====
228
+ function generateTrackPoints() {
229
+ const pts = [];
230
+
231
+ const start = new THREE.Vector3(0, 6, 0);
232
+
233
+ // A) 螺旋上升(3圈,半径15,高度+20)
234
+ const helix1 = generateHelixPoints(start, 15, 20, 3, 1, 260);
235
+ // 1-2) 过渡至波浪段
236
+ const trans1 = generateTransitionPoints(helix1.end, helix1.end.clone().add(new THREE.Vector3(0, 0, 0)), 'end', 60);
237
+ // B) 波浪段(X方向60,长度对应2个波,Y=6)
238
+ const wave = generateWaveYPoints(helix1.end, 60, 5, 2, 6, 220);
239
+ // 2-3) 过渡至漏斗入口
240
+ const trans2 = generateTransitionPoints(wave.end, wave.end.clone().add(new THREE.Vector3(0, 0, 0)), 'end', 60);
241
+ // C) 漏斗下降(半径12->3,下降20,4圈)
242
+ const funnel = generateFunnelPoints(wave.end, 12, 3, 20, 4, -1, 300);
243
+ // 3-4) 过渡至垂直环
244
+ const trans3 = generateTransitionPoints(funnel.end, funnel.end.clone().add(new THREE.Vector3(0, 0, 0)), 'end', 60);
245
+ // D) 垂直环(半径12)
246
+ const loop = generateLoopPoints(funnel.end, 12, 140);
247
+
248
+ // 合并所有点,避免重复起点
249
+ const all = []
250
+ .concat(helix1.points.slice(1))
251
+ .concat(trans1.points.slice(1))
252
+ .concat(wave.points.slice(1))
253
+ .concat(trans2.points.slice(1))
254
+ .concat(funnel.points.slice(1))
255
+ .concat(trans3.points.slice(1))
256
+ .concat(loop.points.slice(1));
257
+
258
+ // 额外平滑,确保连接更自然
259
+ const smoothed = chaikinSmooth(all, 2);
260
+ return smoothed;
261
+ }
262
+
263
+ // ===== 构建轨道(TubeGeometry) =====
264
+ const trackPoints = generateTrackPoints();
265
+ const curve = new THREE.CatmullRomCurve3(trackPoints, true, 'chordal', 0.15);
266
+
267
+ const tubularSegments = 1500; // 建议 800-1500
268
+ const radialSegments = 16; // 建议 12-16
269
+ const tubeRadius = 1.2;
270
+
271
+ const tubeGeo = new THREE.TubeGeometry(curve, tubularSegments, tubeRadius, radialSegments, true);
272
+ const tubeMat = new THREE.MeshStandardMaterial({
273
+ color: 0xffffff,
274
+ roughness: 0.25,
275
+ metalness: 0.1,
276
+ transparent: true,
277
+ opacity: 0.85,
278
+ side: THREE.DoubleSide
279
+ });
280
+ const track = new THREE.Mesh(tubeGeo, tubeMat);
281
+ track.castShadow = true;
282
+ track.receiveShadow = true;
283
+ scene.add(track);
284
+
285
+ // 起点标记
286
+ const startPoint = curve.getPointAt(0);
287
+ const startMarkerGeo = new THREE.SphereGeometry(0.6, 16, 16);
288
+ const startMarkerMat = new THREE.MeshStandardMaterial({ color: 0x44aaff, emissive: 0x0, roughness: 0.5 });
289
+ const startMarker = new THREE.Mesh(startMarkerGeo, startMarkerMat);
290
+ startMarker.position.copy(startPoint);
291
+ startMarker.castShadow = true;
292
+ scene.add(startMarker);
293
+
294
+ // ===== 小球(列车) =====
295
+ const ballGeo = new THREE.SphereGeometry(0.9, 24, 24);
296
+ const ballMat = new THREE.MeshStandardMaterial({ color: 0xff4d4d, roughness: 0.3, metalness: 0.0 });
297
+ const ball = new THREE.Mesh(ballGeo, ballMat);
298
+ ball.castShadow = true;
299
+ scene.add(ball);
300
+
301
+ // ===== 轨道支架系统 =====
302
+ function buildSupports(curve, tubeRadius, groundY = 0, intervalU = 0.02, minHeight = 12) {
303
+ const supports = new THREE.Group();
304
+ const points = [];
305
+ for (let u = 0; u <= 1.0; u += intervalU) {
306
+ points.push(curve.getPointAt(u));
307
+ }
308
+
309
+ const cylGeo = new THREE.CylinderGeometry(0.15, 0.15, 1, 8, 1, true);
310
+ const cylMat = new THREE.MeshStandardMaterial({ color: 0x9aa0a6, metalness: 0.4, roughness: 0.7 });
311
+
312
+ const capGeoTop = new THREE.CylinderGeometry(0.25, 0.25, 0.3, 12);
313
+ const capGeoBottom = new THREE.CylinderGeometry(0.3, 0.3, 0.2, 12);
314
+ const capMat = new THREE.MeshStandardMaterial({ color: 0x8b9098, metalness: 0.2, roughness: 0.8 });
315
+
316
+ for (const p of points) {
317
+ const height = p.y - tubeRadius - groundY;
318
+ if (height < minHeight) continue;
319
+ const cyl = new THREE.Mesh(cylGeo, cylMat);
320
+ cyl.position.set(p.x, groundY + height / 2, p.z);
321
+ cyl.scale.y = height;
322
+ cyl.castShadow = true;
323
+ cyl.receiveShadow = true;
324
+ supports.add(cyl);
325
+
326
+ // 顶部接触轨道的小圆盘
327
+ const capTop = new THREE.Mesh(capGeoTop, capMat);
328
+ capTop.position.set(p.x, p.y - tubeRadius, p.z);
329
+ capTop.castShadow = true;
330
+ supports.add(capTop);
331
+
332
+ // 底部底座
333
+ const capBottom = new THREE.Mesh(capGeoBottom, capMat);
334
+ capBottom.position.set(p.x, groundY + 0.1, p.z);
335
+ capBottom.castShadow = true;
336
+ supports.add(capBottom);
337
+ }
338
+
339
+ return supports;
340
+ }
341
+
342
+ const supports = buildSupports(curve, tubeRadius, 0, 0.025, 10);
343
+ scene.add(supports);
344
+
345
+ // ===== 动画控制 =====
346
+ const clock = new THREE.Clock();
347
+ let t = 0; // 0..1 路径参数
348
+ let speed = 0.06; // 循环速度
349
+ let isFirstPerson = false; // 视角模式切换
350
+ const modeLabel = document.getElementById('mode');
351
+
352
+ function animate() {
353
+ const dt = clock.getDelta();
354
+ t = (t + dt * speed) % 1.0;
355
+
356
+ // 小球沿轨迹运动
357
+ const pos = curve.getPointAt(t);
358
+ const tan = curve.getTangentAt(t);
359
+ ball.position.copy(pos);
360
+ ball.lookAt(pos.clone().add(tan));
361
+
362
+ // 相机跟随
363
+ if (isFirstPerson) {
364
+ // 第一人称:相机位于小球稍后方,看向前进方向
365
+ const backOffset = tan.clone().multiplyScalar(-6);
366
+ const up = new THREE.Vector3(0, 1, 0);
367
+ const side = new THREE.Vector3().crossVectors(tan, up).normalize();
368
+ const camPos = pos.clone().add(backOffset).add(side.multiplyScalar(1.2)).add(up.multiplyScalar(1.5));
369
+ camera.position.lerp(camPos, 0.15);
370
+ const lookAtPoint = pos.clone().add(tan.clone().multiplyScalar(10));
371
+ camera.lookAt(lookAtPoint);
372
+ } else {
373
+ // 第三人称:OrbitControls
374
+ controls.update();
375
+ }
376
+
377
+ renderer.render(scene, camera);
378
+ requestAnimationFrame(animate);
379
+ }
380
+
381
+ animate();
382
+
383
+ // ===== 交互 =====
384
+ window.addEventListener('resize', () => {
385
+ camera.aspect = window.innerWidth / window.innerHeight;
386
+ camera.updateProjectionMatrix();
387
+ renderer.setSize(window.innerWidth, window.innerHeight);
388
+ });
389
+
390
+ window.addEventListener('keydown', (e) => {
391
+ if (e.key.toLowerCase() === 'v') {
392
+ isFirstPerson = !isFirstPerson;
393
+ modeLabel.textContent = isFirstPerson ? '第一人称' : '第三人称';
394
+ }
395
+ if (e.key === '+' || e.key === '=') {
396
+ speed = Math.min(0.2, speed + 0.01);
397
+ }
398
+ if (e.key === '-' || e.key === '_') {
399
+ speed = Math.max(0.01, speed - 0.01);
400
+ }
401
+ });
402
+
403
+ // 鼠标右键禁用默认菜单,便于平移操作
404
+ window.addEventListener('contextmenu', (e) => e.preventDefault(), false);
405
+ </script>
406
+ </body>
407
+ </html>