Spaces:
Running
Running
Upload frontend/cave3d.js
Browse files- frontend/cave3d.js +677 -31
frontend/cave3d.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
/* =========================================================================
|
| 2 |
CaveScene — hang 3D đầy đủ như hangmu.online gốc:
|
| 3 |
vòm + sàn bậc + hố lửa + nước + đuốc + màn hình lớn (board) + vòng huyền thoại
|
| 4 |
-
+ hội trường đá (benches) + lối vào
|
|
|
|
| 5 |
========================================================================= */
|
| 6 |
import * as THREE from 'three'
|
| 7 |
import React, { useMemo, useRef, useLayoutEffect, useState, useEffect } from 'react'
|
|
@@ -335,7 +336,7 @@ function TorchLight({ seed }) {
|
|
| 335 |
}
|
| 336 |
|
| 337 |
/* =========================================================================
|
| 338 |
-
LỐI VÀO (south +z) — arch + tunnel + exit light
|
| 339 |
========================================================================= */
|
| 340 |
function Entrance() {
|
| 341 |
const archGeo = useMemo(() => {
|
|
@@ -360,10 +361,16 @@ function Entrance() {
|
|
| 360 |
<mesh geometry={tunnelGeo} position={[0, 2.6, 9]}>
|
| 361 |
<meshLambertMaterial vertexColors side={THREE.DoubleSide} />
|
| 362 |
</mesh>
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
<
|
|
|
|
| 366 |
</mesh>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
<pointLight position={[0, 5, 1]} color="#ffe2b8" intensity={30} distance={22} decay={1.6} />
|
| 368 |
</group>
|
| 369 |
)
|
|
@@ -371,7 +378,6 @@ function Entrance() {
|
|
| 371 |
|
| 372 |
/* =========================================================================
|
| 373 |
MÀN HÌNH LỚN (board) — plane + texture "BẢNG TIN HANG MU" + khung gỗ
|
| 374 |
-
(video thật qua CSS3D projection — phần app.js)
|
| 375 |
========================================================================= */
|
| 376 |
export const BOARD = { w: 24, h: 14, y: 7.3, z: -47.5 }
|
| 377 |
function Board() {
|
|
@@ -428,7 +434,7 @@ function Board() {
|
|
| 428 |
}
|
| 429 |
|
| 430 |
/* =========================================================================
|
| 431 |
-
VÒNG HUYỀN THOẠI — 8 ghế đá quanh hố lửa
|
| 432 |
========================================================================= */
|
| 433 |
const RING_SLOTS = 8
|
| 434 |
const RING_R = 8.5
|
|
@@ -448,7 +454,6 @@ function LegendRing() {
|
|
| 448 |
list.push({ name: row.name, number: row.number, wins: row.wins, game: g, rank: 1 })
|
| 449 |
}
|
| 450 |
}
|
| 451 |
-
// nếu chưa đủ 8 → thêm người đang trong hang (tránh đứng top)
|
| 452 |
for (const p of players.values()) {
|
| 453 |
if (list.length >= RING_SLOTS) break
|
| 454 |
if (seen.has(p.name)) continue
|
|
@@ -465,7 +470,7 @@ function LegendRing() {
|
|
| 465 |
const ang = (i / RING_SLOTS) * Math.PI * 2
|
| 466 |
const bob = Math.sin(t * 1.6 + i * 1.1) * 0.03
|
| 467 |
child.position.set(Math.sin(ang) * RING_R, 0.25 + bob, Math.cos(ang) * RING_R)
|
| 468 |
-
child.rotation.y = -ang + Math.PI
|
| 469 |
})
|
| 470 |
})
|
| 471 |
return (
|
|
@@ -561,6 +566,656 @@ function Benches() {
|
|
| 561 |
</group>
|
| 562 |
)
|
| 563 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
|
| 565 |
/* =========================================================================
|
| 566 |
TỔNG HỢP CaveScene
|
|
@@ -579,31 +1234,22 @@ export function CaveScene() {
|
|
| 579 |
<Board />
|
| 580 |
<LegendRing />
|
| 581 |
<Benches />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 582 |
<Entrance />
|
|
|
|
| 583 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />
|
| 584 |
<ambientLight intensity={0.7} color={0xf4cba0} />
|
| 585 |
</>
|
| 586 |
)
|
| 587 |
}
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
const out = []
|
| 591 |
-
for (let t = 1; t <= CAVE.tiers; t++) {
|
| 592 |
-
const r = CAVE.pitRadius + (t - 1) * CAVE.tierWidth + 1.25
|
| 593 |
-
const y = -CAVE.pitDepth + (t - 1) * tierH + 0.23
|
| 594 |
-
out.push({ r, y, t })
|
| 595 |
-
}
|
| 596 |
-
return out
|
| 597 |
-
}, [])
|
| 598 |
-
return (
|
| 599 |
-
<group>
|
| 600 |
-
{items.map((it, i) => (
|
| 601 |
-
<mesh key={i} position={[0, it.y, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
| 602 |
-
<ringGeometry args={[it.r - 1.3, it.r + 1.2, 72]} />
|
| 603 |
-
<meshLambertMaterial color="#1c1612" roughness={0.6} metalness={0.5} side={THREE.DoubleSide} />
|
| 604 |
-
</mesh>
|
| 605 |
-
))}
|
| 606 |
-
</group>
|
| 607 |
-
)
|
| 608 |
-
}
|
| 609 |
-
export { floorHeight, domeHeight }
|
|
|
|
| 1 |
/* =========================================================================
|
| 2 |
CaveScene — hang 3D đầy đủ như hangmu.online gốc:
|
| 3 |
vòm + sàn bậc + hố lửa + nước + đuốc + màn hình lớn (board) + vòng huyền thoại
|
| 4 |
+
+ hội trường đá (benches) + lối vào + sân ngoài + tường huyền thoại
|
| 5 |
+
+ bảng EPL + ngày này năm xưa + cúp + banner + nộm + sân chơi
|
| 6 |
========================================================================= */
|
| 7 |
import * as THREE from 'three'
|
| 8 |
import React, { useMemo, useRef, useLayoutEffect, useState, useEffect } from 'react'
|
|
|
|
| 336 |
}
|
| 337 |
|
| 338 |
/* =========================================================================
|
| 339 |
+
LỐI VÀO (south +z) — arch + tunnel + exit light + vành sáng + vòm đá
|
| 340 |
========================================================================= */
|
| 341 |
function Entrance() {
|
| 342 |
const archGeo = useMemo(() => {
|
|
|
|
| 361 |
<mesh geometry={tunnelGeo} position={[0, 2.6, 9]}>
|
| 362 |
<meshLambertMaterial vertexColors side={THREE.DoubleSide} />
|
| 363 |
</mesh>
|
| 364 |
+
{/* vành sáng + vòm đá quanh cửa hang (thay tấm chặn mờ) */}
|
| 365 |
+
<mesh position={[0, 3.2, 17.6]}>
|
| 366 |
+
<ringGeometry args={[6.3, 7.5, 30]} />
|
| 367 |
+
<meshLambertMaterial color="#6b5540" side={THREE.DoubleSide} fog={false} />
|
| 368 |
</mesh>
|
| 369 |
+
<mesh position={[0, 3.3, 17.3]}>
|
| 370 |
+
<torusGeometry args={[6.9, 0.72, 8, 26, Math.PI]} />
|
| 371 |
+
<meshLambertMaterial color="#8a6f55" flatShading side={THREE.DoubleSide} fog={false} />
|
| 372 |
+
</mesh>
|
| 373 |
+
<pointLight position={[0, 10, 15.5]} color="#ffe9c8" intensity={45} distance={30} decay={1.2} fog={false} />
|
| 374 |
<pointLight position={[0, 5, 1]} color="#ffe2b8" intensity={30} distance={22} decay={1.6} />
|
| 375 |
</group>
|
| 376 |
)
|
|
|
|
| 378 |
|
| 379 |
/* =========================================================================
|
| 380 |
MÀN HÌNH LỚN (board) — plane + texture "BẢNG TIN HANG MU" + khung gỗ
|
|
|
|
| 381 |
========================================================================= */
|
| 382 |
export const BOARD = { w: 24, h: 14, y: 7.3, z: -47.5 }
|
| 383 |
function Board() {
|
|
|
|
| 434 |
}
|
| 435 |
|
| 436 |
/* =========================================================================
|
| 437 |
+
VÒNG HUYỀN THOẠI — 8 ghế đá quanh hố lửa (leaderboard top)
|
| 438 |
========================================================================= */
|
| 439 |
const RING_SLOTS = 8
|
| 440 |
const RING_R = 8.5
|
|
|
|
| 454 |
list.push({ name: row.name, number: row.number, wins: row.wins, game: g, rank: 1 })
|
| 455 |
}
|
| 456 |
}
|
|
|
|
| 457 |
for (const p of players.values()) {
|
| 458 |
if (list.length >= RING_SLOTS) break
|
| 459 |
if (seen.has(p.name)) continue
|
|
|
|
| 470 |
const ang = (i / RING_SLOTS) * Math.PI * 2
|
| 471 |
const bob = Math.sin(t * 1.6 + i * 1.1) * 0.03
|
| 472 |
child.position.set(Math.sin(ang) * RING_R, 0.25 + bob, Math.cos(ang) * RING_R)
|
| 473 |
+
child.rotation.y = -ang + Math.PI
|
| 474 |
})
|
| 475 |
})
|
| 476 |
return (
|
|
|
|
| 566 |
</group>
|
| 567 |
)
|
| 568 |
}
|
| 569 |
+
function BenchRings() {
|
| 570 |
+
const items = useMemo(() => {
|
| 571 |
+
const out = []
|
| 572 |
+
for (let t = 1; t <= CAVE.tiers; t++) {
|
| 573 |
+
const r = CAVE.pitRadius + (t - 1) * CAVE.tierWidth + 1.25
|
| 574 |
+
const y = -CAVE.pitDepth + (t - 1) * tierH + 0.23
|
| 575 |
+
out.push({ r, y, t })
|
| 576 |
+
}
|
| 577 |
+
return out
|
| 578 |
+
}, [])
|
| 579 |
+
return (
|
| 580 |
+
<group>
|
| 581 |
+
{items.map((it, i) => (
|
| 582 |
+
<mesh key={i} position={[0, it.y, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
| 583 |
+
<ringGeometry args={[it.r - 1.3, it.r + 1.2, 72]} />
|
| 584 |
+
<meshLambertMaterial color="#1c1612" roughness={0.6} metalness={0.5} side={THREE.DoubleSide} />
|
| 585 |
+
</mesh>
|
| 586 |
+
))}
|
| 587 |
+
</group>
|
| 588 |
+
)
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
/* =========================================================================
|
| 592 |
+
BẢNG XẾP HẠNG NGOẠI HẠNG ANH (EPL) — texture như gốc
|
| 593 |
+
========================================================================= */
|
| 594 |
+
const EPL_TEAMS = [
|
| 595 |
+
['Arsenal', 38, 28, 5, 5, 91, 29, 89, 'ucl'], ['Man City', 38, 28, 7, 3, 96, 34, 91, 'ucl'],
|
| 596 |
+
['Liverpool', 38, 24, 10, 4, 86, 41, 82, 'ucl'], ['Aston Villa', 38, 20, 8, 10, 76, 61, 68, 'ucl'],
|
| 597 |
+
['Tottenham', 38, 20, 6, 12, 74, 61, 66, 'europe'], ['Chelsea', 38, 18, 9, 11, 77, 63, 63, 'europe'],
|
| 598 |
+
['Newcastle', 38, 18, 6, 14, 85, 62, 60, 'europe'], ['Man United', 38, 18, 6, 14, 57, 58, 60, 'europe'],
|
| 599 |
+
['West Ham', 38, 14, 10, 14, 60, 74, 52, ''], ['Crystal Palace', 38, 13, 10, 15, 57, 58, 49, ''],
|
| 600 |
+
['Brighton', 38, 12, 12, 14, 55, 62, 48, ''], ['Bournemouth', 38, 13, 9, 16, 54, 67, 48, ''],
|
| 601 |
+
['Fulham', 38, 13, 8, 17, 55, 61, 47, ''], ['Wolves', 38, 13, 7, 18, 50, 65, 46, ''],
|
| 602 |
+
['Everton', 38, 13, 9, 16, 40, 51, 40, ''], ['Brentford', 38, 10, 9, 19, 56, 65, 39, ''],
|
| 603 |
+
['Nottm Forest', 38, 9, 9, 20, 49, 67, 32, ''], ['Luton', 38, 6, 8, 24, 52, 85, 26, 'relegation'],
|
| 604 |
+
['Burnley', 38, 5, 9, 24, 41, 78, 24, 'relegation'], ['Sheffield Utd', 38, 3, 7, 28, 35, 104, 16, 'relegation'],
|
| 605 |
+
]
|
| 606 |
+
function makeEplTex() {
|
| 607 |
+
const c = document.createElement('canvas'); c.width = 1400; c.height = 1560
|
| 608 |
+
const g = c.getContext('2d')
|
| 609 |
+
const gr = g.createLinearGradient(0, 0, 0, 1560)
|
| 610 |
+
gr.addColorStop(0, '#5c0e1b'); gr.addColorStop(0.5, '#3a0812'); gr.addColorStop(1, '#1b1410')
|
| 611 |
+
g.fillStyle = gr; g.fillRect(0, 0, 1400, 1560)
|
| 612 |
+
g.strokeStyle = 'rgba(255,240,220,0.12)'; g.lineWidth = 14; g.strokeRect(7, 7, 1386, 1546)
|
| 613 |
+
g.textAlign = 'center'; g.fillStyle = '#8a1a2a'
|
| 614 |
+
g.font = '900 96px Bitter, Georgia, serif'; g.fillText('NGOẠI HẠNG ANH', 700, 130)
|
| 615 |
+
g.fillStyle = 'rgba(255,240,220,0.7)'; g.font = '600 42px "Be Vietnam Pro", sans-serif'
|
| 616 |
+
g.fillText('Bảng xếp hạng mùa giải', 700, 195)
|
| 617 |
+
const cols = [700, 1070, 1180, 1280]
|
| 618 |
+
g.fillStyle = 'rgba(201,162,78,0.85)'; g.font = '600 32px "Be Vietnam Pro", sans-serif'
|
| 619 |
+
;['Đội', 'T', 'H', 'Đ'].forEach((t, i) => g.fillText(t, cols[i], 250))
|
| 620 |
+
for (let i = 0; i < EPL_TEAMS.length; i++) {
|
| 621 |
+
const t = EPL_TEAMS[i]
|
| 622 |
+
const y = 300 + i * 58
|
| 623 |
+
g.fillStyle = i % 2 ? 'rgba(255,240,220,0.045)' : 'rgba(255,240,220,0.0)'
|
| 624 |
+
g.fillRect(20, y - 22, 1360, 56)
|
| 625 |
+
if (i === 7) { g.fillStyle = 'rgba(142,11,32,0.5)'; g.fillRect(20, y - 22, 1360, 56) }
|
| 626 |
+
g.textAlign = 'left'; g.fillStyle = i === 7 ? '#ffd76a' : (t[7] ? '#8e0b20' : 'rgba(255,240,220,0.85)')
|
| 627 |
+
g.font = i === 7 ? '800 42px Bitter' : '600 42px "Be Vietnam Pro", sans-serif'
|
| 628 |
+
g.fillText(t[0], 60, y + 8)
|
| 629 |
+
g.textAlign = 'center'
|
| 630 |
+
g.fillStyle = 'rgba(255,240,220,0.7)'
|
| 631 |
+
;[t[1], t[5] - t[6], t[6]].forEach((v, k) => { g.fillText(String(v), cols[k + 1], y + 8) })
|
| 632 |
+
g.fillStyle = i === 7 ? '#ffd76a' : 'rgba(255,240,220,0.95)'
|
| 633 |
+
g.font = '800 44px Bitter'; g.fillText(String(t[7]), 1310, y + 8)
|
| 634 |
+
}
|
| 635 |
+
g.textAlign = 'right'; g.fillStyle = 'rgba(255,240,220,0.4)'; g.font = '500 28px "Be Vietnam Pro", sans-serif'
|
| 636 |
+
g.fillText('Dự Cúp C1 · Cúp châu Âu · Xuống hạng · Đ = Điểm', 1360, 1540)
|
| 637 |
+
const tx = new THREE.CanvasTexture(c); tx.colorSpace = THREE.SRGBColorSpace; tx.anisotropy = 16
|
| 638 |
+
return tx
|
| 639 |
+
}
|
| 640 |
+
function EplBoard() {
|
| 641 |
+
const [tex, setTex] = React.useState(null)
|
| 642 |
+
React.useEffect(() => {
|
| 643 |
+
let alive = true
|
| 644 |
+
const t = makeEplTex()
|
| 645 |
+
if (alive && t) setTex(t)
|
| 646 |
+
return () => { alive = false }
|
| 647 |
+
}, [])
|
| 648 |
+
if (!tex) return null
|
| 649 |
+
return (
|
| 650 |
+
<group position={[-18.5, 6.5, -44]} rotation={[0, 0.52, 0]}>
|
| 651 |
+
<mesh position={[0, 0, -0.08]}>
|
| 652 |
+
<boxGeometry args={[8.4, 9.4, 0.28]} />
|
| 653 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 654 |
+
</mesh>
|
| 655 |
+
<mesh position={[0, 0, 0.1]}>
|
| 656 |
+
<planeGeometry args={[8.0, 9.0]} />
|
| 657 |
+
<meshBasicMaterial map={tex} transparent toneMapped={false} fog={false} />
|
| 658 |
+
</mesh>
|
| 659 |
+
</group>
|
| 660 |
+
)
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
/* =========================================================================
|
| 664 |
+
NGÀY NÀY NĂM XƯA — bảng lịch sử, đổi theo ngày thật
|
| 665 |
+
========================================================================= */
|
| 666 |
+
const ON_THIS_DAY = [
|
| 667 |
+
{ m: 1, d: 21, y: 2017, title: 'Rooney thành vua phá lưới CLB', text: 'Cú sút phạt ở phút bù giờ trước Stoke là bàn thứ 250 — vượt kỷ lục của Sir Bobby Charlton.' },
|
| 668 |
+
{ m: 2, d: 5, y: 1985, title: 'Cristiano Ronaldo chào đời', text: 'Sinh ở Madeira. 2008: Quả bóng Vàng đầu tiên trong màu áo Quỷ Đỏ.' },
|
| 669 |
+
{ m: 2, d: 6, y: 1958, title: 'Thảm hoạ Munich', text: 'Chuyến bay chở đội bóng gặp nạn trên đường về từ Belgrade — 8 cầu thủ "Busby Babes" ra đi. Mãi không quên.' },
|
| 670 |
+
{ m: 2, d: 19, y: 1910, title: 'Trận đầu tiên ở Old Trafford', text: 'Sân mới khai trương với trận gặp Liverpool. "Nhà hát của những giấc mơ" bắt đầu từ đây.' },
|
| 671 |
+
{ m: 2, d: 19, y: 1958, title: 'Trận đầu sau Munich', text: '13 ngày sau thảm hoạ, MU thắng Sheffield Wednesday 3–0 ở FA Cup trong nước mắt cả Old Trafford.' },
|
| 672 |
+
{ m: 2, d: 21, y: 1958, title: 'Duncan Edwards ra đi', text: 'Viên ngọc của "Busby Babes" qua đời sau 15 ngày chiến đấu với vết thương từ Munich, mới 21 tuổi.' },
|
| 673 |
+
{ m: 2, d: 24, y: 1940, title: 'Denis Law chào đời', text: '"The King" của Stretford End, Quả bóng Vàng 1964.' },
|
| 674 |
+
{ m: 2, d: 26, y: 1973, title: 'Ole Gunnar Solskjær chào đời', text: 'Người ghi bàn thắng định mệnh ở Camp Nou 1999.' },
|
| 675 |
+
{ m: 3, d: 2, y: 1991, title: 'Ryan Giggs ra mắt', text: 'Trận đầu tiên của cầu thủ sau này khoác áo MU nhiều trận nhất lịch sử.' },
|
| 676 |
+
{ m: 3, d: 4, y: 1995, title: 'Thắng Ipswich 9–0', text: 'Chiến thắng đậm nhất lịch sử Premier League lúc đó — Andy Cole ghi 5 bàn.' },
|
| 677 |
+
{ m: 4, d: 14, y: 1999, title: 'Siêu phẩm của Giggs', text: 'Bán kết lại FA Cup với Arsenal: Giggs solo qua cả hàng thủ ở hiệp phụ, cởi áo ăn mừng.' },
|
| 678 |
+
{ m: 4, d: 21, y: 1999, title: 'Ngược dòng ở Turin', text: 'Bị Juventus dẫn 2–0, Keane khơi mào, MU thắng 3–2 vào chung kết Champions League.' },
|
| 679 |
+
{ m: 4, d: 22, y: 2013, title: 'Chức vô địch thứ 20', text: 'Van Persie lập hat-trick vào lưới Aston Villa, MU vô địch Ngoại hạng Anh lần thứ 20.' },
|
| 680 |
+
{ m: 5, d: 8, y: 2013, title: 'Sir Alex chia tay', text: 'Sau hơn 26 năm và 38 danh hiệu, Sir Alex Ferguson thông báo nghỉ hưu.' },
|
| 681 |
+
{ m: 5, d: 11, y: 1996, title: 'FA Cup 1996 — cú đúp thứ hai', text: 'Cantona ghi bàn duy nhất vào lưới Liverpool, MU giành cú đúp quốc nội lần thứ hai.' },
|
| 682 |
+
{ m: 5, d: 14, y: 1999, title: 'Cúp C2 châu Âu', text: 'Thắng Barcelona 2–1 ở Rotterdam, Mark Hughes lập cú đúp.' },
|
| 683 |
+
{ m: 5, d: 16, y: 1999, title: 'Mảnh ghép thứ nhất của cú ăn ba', text: 'Thắng Tottenham 2–1 ngày cuối mùa, giành chức vô địch Premier League.' },
|
| 684 |
+
{ m: 5, d: 17, y: 1990, title: 'Danh hiệu đầu tiên của Sir Alex', text: 'Đá lại chung kết FA Cup, Lee Martin ghi bàn duy nhất vào lưới Crystal Palace.' },
|
| 685 |
+
{ m: 5, d: 21, y: 2008, title: 'Vô địch châu Âu ở Moscow', text: 'Hoà Chelsea 1–1 dưới mưa, Van der Sar cản phạt Anelka — MU lần thứ ba vô địch châu Âu.' },
|
| 686 |
+
{ m: 5, d: 22, y: 1999, title: 'Mảnh ghép thứ hai của cú ăn ba', text: 'Thắng Newcastle 2–0 ở chung kết FA Cup — Sheringham và Scholes ghi bàn.' },
|
| 687 |
+
{ m: 5, d: 24, y: 2017, title: 'Vô địch Europa League', text: 'Thắng Ajax 2–0 ở Stockholm — danh hiệu châu Âu duy nhất còn thiếu trong tủ.' },
|
| 688 |
+
{ m: 5, d: 25, y: 2024, title: 'FA Cup 2024', text: 'Thắng Man City 2–1 ở chung kết — Garnacho và Mainoo ghi bàn.' },
|
| 689 |
+
{ m: 5, d: 26, y: 1999, title: 'Cú ăn ba — Camp Nou', text: 'Bị Bayern dẫn tới phút 90, Sheringham rồi Solskjær ghi bàn bù giờ — MU vô địch châu Âu, đúng sinh nhật Sir Matt.' },
|
| 690 |
+
{ m: 5, d: 29, y: 1968, title: 'Lần đầu vô địch châu Âu', text: 'Thắng Benfica 4–1 sau hiệp phụ ở Wembley — 10 năm sau Munich. Charlton lập cú đúp.' },
|
| 691 |
+
{ m: 8, d: 17, y: 1996, title: 'Beckham ghi bàn từ giữa sân', text: 'Cú sút từ vạch giữa sân vào lưới Wimbledon khiến cả nước Anh biết tên số 10 trẻ tuổi.' },
|
| 692 |
+
{ m: 8, d: 28, y: 2011, title: 'Thắng Arsenal 8–2', text: 'Rooney lập hat-trick ở Old Trafford trong một chiều không thể quên.' },
|
| 693 |
+
{ m: 9, d: 11, y: 2021, title: 'Ronaldo trở về', text: 'Lần thứ hai ra mắt ở Old Trafford, lập cú đúp vào lưới Newcastle.' },
|
| 694 |
+
{ m: 10, d: 24, y: 1985, title: 'Wayne Rooney chào đời', text: 'Chân sút vĩ đại nhất lịch sử CLB với 253 bàn thắng.' },
|
| 695 |
+
{ m: 11, d: 6, y: 1986, title: 'Sir Alex Ferguson nhậm chức', text: 'Ngày một người Scotland tới Old Trafford — mở ra 26 năm và 38 danh hiệu.' },
|
| 696 |
+
{ m: 12, d: 2, y: 2008, title: 'Ronaldo đoạt Quả bóng Vàng', text: 'Cầu thủ MU đầu tiên giành Quả bóng Vàng sau George Best 1968.' },
|
| 697 |
+
{ m: 12, d: 31, y: 1941, title: 'Sir Alex Ferguson chào đời', text: 'HLV vĩ đại nhất lịch sử bóng đá Anh, sinh ở Govan, Glasgow.' },
|
| 698 |
+
]
|
| 699 |
+
function OnThisDayBoard() {
|
| 700 |
+
const [tex, setTex] = React.useState(null)
|
| 701 |
+
React.useEffect(() => {
|
| 702 |
+
let alive = true
|
| 703 |
+
const c = document.createElement('canvas'); c.width = 900; c.height = 1000
|
| 704 |
+
const g = c.getContext('2d')
|
| 705 |
+
const gr = g.createLinearGradient(0, 0, 0, 1000)
|
| 706 |
+
gr.addColorStop(0, '#4f0c18'); gr.addColorStop(0.6, '#2a060d'); gr.addColorStop(1, '#180307')
|
| 707 |
+
g.fillStyle = gr; g.fillRect(0, 0, 900, 1000)
|
| 708 |
+
g.strokeStyle = 'rgba(201,162,78,0.8)'; g.lineWidth = 8; g.strokeRect(8, 8, 884, 984)
|
| 709 |
+
g.textAlign = 'center'; g.fillStyle = '#c9a24e'; g.font = '800 44px Bitter, Georgia, serif'
|
| 710 |
+
g.fillText('NGÀY NÀY NĂM XƯA', 450, 80)
|
| 711 |
+
const now = new Date()
|
| 712 |
+
const today = ON_THIS_DAY.filter((e) => e.m === now.getMonth() + 1 && e.d === now.getDate())
|
| 713 |
+
g.fillStyle = 'rgba(255,240,220,0.5)'; g.font = '600 28px "Be Vietnam Pro", sans-serif'
|
| 714 |
+
g.fillText(today.length ? '' + today.length + ' mốc son hôm nay' : 'Hôm nay chưa có dấu mốc nào — hãy tạo nên lịch sử!', 450, 125)
|
| 715 |
+
if (today.length) {
|
| 716 |
+
today.forEach((e, i) => {
|
| 717 |
+
const y = 210 + i * 210
|
| 718 |
+
g.fillStyle = 'rgba(142,11,32,0.45)'; g.fillRect(60, y - 60, 780, 170)
|
| 719 |
+
g.strokeStyle = 'rgba(201,162,78,0.6)'; g.lineWidth = 3; g.strokeRect(60, y - 60, 780, 170)
|
| 720 |
+
g.textAlign = 'left'; g.fillStyle = '#ffd76a'; g.font = '700 38px "Be Vietnam Pro", sans-serif'
|
| 721 |
+
g.fillText(e.d + ' THÁNG ' + e.m + ' · ' + e.y, 90, y - 22)
|
| 722 |
+
g.fillStyle = '#fff2c8'; g.font = '800 52px Bitter, Georgia, serif'
|
| 723 |
+
g.fillText(e.title.slice(0, 40), 90, y + 25)
|
| 724 |
+
g.fillStyle = 'rgba(255,240,220,0.8)'; g.font = '500 26px "Be Vietnam Pro", sans-serif'
|
| 725 |
+
g.fillText(e.text.length > 62 ? e.text.slice(0, 60) + '…' : e.text, 90, y + 78, 720)
|
| 726 |
+
})
|
| 727 |
+
}
|
| 728 |
+
const tx = new THREE.CanvasTexture(c); tx.colorSpace = THREE.SRGBColorSpace; tx.anisotropy = 16
|
| 729 |
+
if (alive) setTex(tx)
|
| 730 |
+
return () => { alive = false }
|
| 731 |
+
}, [])
|
| 732 |
+
if (!tex) return null
|
| 733 |
+
return (
|
| 734 |
+
<group position={[18.5, 6.5, -44]} rotation={[0, -0.5, 0]}>
|
| 735 |
+
<mesh position={[0, 0, -0.08]}>
|
| 736 |
+
<boxGeometry args={[6.6, 7.3, 0.28]} />
|
| 737 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 738 |
+
</mesh>
|
| 739 |
+
<mesh position={[0, 0, 0.1]}>
|
| 740 |
+
<planeGeometry args={[6.2, 6.9]} />
|
| 741 |
+
<meshBasicMaterial map={tex} transparent toneMapped={false} fog={false} />
|
| 742 |
+
</mesh>
|
| 743 |
+
</group>
|
| 744 |
+
)
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
/* =========================================================================
|
| 748 |
+
TƯỜNG HUYỀN THOẠI — 3 bảng lớn quanh vòm + 10 chân dung
|
| 749 |
+
========================================================================= */
|
| 750 |
+
const LEGEND_PANELS = [
|
| 751 |
+
{ kicker: 'DI TÍCH LỊCH SỬ', title: 'Hang MU', sub: 'Nơi anh em fan Quỷ Đỏ tụ họp' },
|
| 752 |
+
{ kicker: 'MANCHESTER UNITED', title: 'Từ 1878', sub: 'Glory Glory Man United' },
|
| 753 |
+
{ kicker: 'TƯỜNG HUYỀN THOẠI', title: 'Thế hệ vàng', sub: 'Mười huyền thoại khoác áo đỏ' },
|
| 754 |
+
]
|
| 755 |
+
function drawLegendPanel(t) {
|
| 756 |
+
const c = document.createElement('canvas'); c.width = 1024; c.height = 512
|
| 757 |
+
const g = c.getContext('2d')
|
| 758 |
+
const r = g.createRadialGradient(307, 0, 40, 410, 205, 820)
|
| 759 |
+
r.addColorStop(0, '#96202f'); r.addColorStop(0.5, '#7a1424'); r.addColorStop(1, '#4f0c18')
|
| 760 |
+
g.fillStyle = r; g.fillRect(0, 0, 1024, 512)
|
| 761 |
+
g.strokeStyle = '#c9a24e'; g.lineWidth = 22; g.strokeRect(11, 11, 1002, 490)
|
| 762 |
+
g.strokeStyle = 'rgba(236,208,140,0.75)'; g.lineWidth = 4; g.strokeRect(40, 40, 944, 432)
|
| 763 |
+
for (const [x, y] of [[62, 62], [962, 62], [62, 450], [962, 450]]) {
|
| 764 |
+
const rr = g.createRadialGradient(x - 3, y - 3, 1, x, y, 11)
|
| 765 |
+
rr.addColorStop(0, '#f6e2a6'); rr.addColorStop(0.6, '#c9a24e'); rr.addColorStop(1, '#5e4719')
|
| 766 |
+
g.fillStyle = rr; g.beginPath(); g.arc(x, y, 11, 0, Math.PI * 2); g.fill()
|
| 767 |
+
}
|
| 768 |
+
g.textAlign = 'center'; g.textBaseline = 'middle'; g.shadowColor = 'rgba(0,0,0,0.6)'; g.shadowOffsetY = 4; g.shadowBlur = 2
|
| 769 |
+
g.fillStyle = '#c9a24e'; g.font = '800 46px Bitter, Georgia, serif'
|
| 770 |
+
if ('letterSpacing' in g) { g.letterSpacing = '10px' }
|
| 771 |
+
g.fillText(t.kicker, 512, 118, 864)
|
| 772 |
+
if ('letterSpacing' in g) { g.letterSpacing = '0px' }
|
| 773 |
+
g.fillStyle = '#ecd08c'; g.font = '900 150px Bitter, Georgia, serif'
|
| 774 |
+
g.fillText(t.title, 512, 250, 864)
|
| 775 |
+
g.shadowColor = 'transparent'
|
| 776 |
+
g.fillStyle = '#c9a24e'; g.fillRect(150, 342, 724, 4)
|
| 777 |
+
g.fillStyle = '#efe9dd'; g.font = '600 48px "Be Vietnam Pro", system-ui, sans-serif'
|
| 778 |
+
g.fillText(t.sub, 512, 410, 864)
|
| 779 |
+
const tx = new THREE.CanvasTexture(c); tx.colorSpace = THREE.SRGBColorSpace; tx.anisotropy = 16
|
| 780 |
+
return tx
|
| 781 |
+
}
|
| 782 |
+
const LEGEND_SLOTS = [
|
| 783 |
+
[-22.5, 11.4, -40.5, 0.52], [-13.4, 8.6, -45.6, 0.9], [12.8, 9.8, -46.5, 1.9],
|
| 784 |
+
]
|
| 785 |
+
function LegendWall() {
|
| 786 |
+
const [texs, setTexs] = React.useState(null)
|
| 787 |
+
React.useEffect(() => {
|
| 788 |
+
let alive = true
|
| 789 |
+
Promise.all([
|
| 790 |
+
document.fonts.load('900 150px Bitter', 'Hang MU').catch(() => 0),
|
| 791 |
+
document.fonts.load('800 46px Bitter', 'TƯỜNG HUYỀN THOẠI').catch(() => 0),
|
| 792 |
+
document.fonts.load('600 48px "Be Vietnam Pro"', 'Tụ họp').catch(() => 0),
|
| 793 |
+
]).then(() => {
|
| 794 |
+
const t = LEGEND_PANELS.map(drawLegendPanel)
|
| 795 |
+
if (alive) setTexs(t)
|
| 796 |
+
})
|
| 797 |
+
return () => { alive = false }
|
| 798 |
+
}, [])
|
| 799 |
+
if (!texs) return null
|
| 800 |
+
return (
|
| 801 |
+
<group>
|
| 802 |
+
{LEGEND_SLOTS.map(([x, y, z, rot], i) => (
|
| 803 |
+
<group key={i} position={[x, y, z]} rotation={[0, rot, 0]}>
|
| 804 |
+
<mesh position={[0, 0, -0.12]}>
|
| 805 |
+
<boxGeometry args={[8.9, 4.45, 0.3]} />
|
| 806 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 807 |
+
</mesh>
|
| 808 |
+
<mesh position={[0, 0, 0.06]}>
|
| 809 |
+
<planeGeometry args={[8.5, 4.25]} />
|
| 810 |
+
<meshBasicMaterial map={texs[i]} transparent toneMapped={false} fog={false} />
|
| 811 |
+
</mesh>
|
| 812 |
+
</group>
|
| 813 |
+
))}
|
| 814 |
+
</group>
|
| 815 |
+
)
|
| 816 |
+
}
|
| 817 |
+
const LEGENDS = [
|
| 818 |
+
['rooney', 'Wayne Rooney', '2004–2017', 24, -42], ['ronaldo', 'Cristiano Ronaldo', '2003–2009, 2021–2022', 28, -40],
|
| 819 |
+
['giggs', 'Ryan Giggs', '1991–2014', 20, -48], ['scholes', 'Paul Scholes', '1994–2013', 14, -50],
|
| 820 |
+
['neville', 'Gary Neville', '1992–2011', -26, -44], ['ferdinand', 'Rio Ferdinand', '2002–2014', -20, -48],
|
| 821 |
+
['vidic', 'Nemanja Vidić', '2006–2014', -14, -50], ['vandersar', 'Edwin van der Sar', '2005–2011', -28, -40],
|
| 822 |
+
['carrick', 'Michael Carrick', '2006–2018', 8, -52], ['evra', 'Patrice Evra', '2006–2014', -8, -52],
|
| 823 |
+
]
|
| 824 |
+
function LegendPortraits() {
|
| 825 |
+
const [textures, setTextures] = React.useState(null)
|
| 826 |
+
React.useEffect(() => {
|
| 827 |
+
let alive = true
|
| 828 |
+
const loader = new THREE.TextureLoader()
|
| 829 |
+
const out = {}
|
| 830 |
+
let done = 0
|
| 831 |
+
LEGENDS.forEach(([key]) => {
|
| 832 |
+
loader.load('/images/legends/' + key + '.jpg', (t) => {
|
| 833 |
+
t.colorSpace = THREE.SRGBColorSpace
|
| 834 |
+
out[key] = t
|
| 835 |
+
done++
|
| 836 |
+
if (done >= LEGENDS.length && alive) setTextures({ ...out })
|
| 837 |
+
}, undefined, () => {
|
| 838 |
+
done++
|
| 839 |
+
if (done >= LEGENDS.length && alive) setTextures({ ...out })
|
| 840 |
+
})
|
| 841 |
+
})
|
| 842 |
+
return () => { alive = false }
|
| 843 |
+
}, [])
|
| 844 |
+
return (
|
| 845 |
+
<group>
|
| 846 |
+
{LEGENDS.map(([key, name, years, x, z]) => (
|
| 847 |
+
<group key={key} position={[x, 3.6, z]} rotation={[0, 0.9, 0]}>
|
| 848 |
+
<mesh position={[0, 0, -0.06]}>
|
| 849 |
+
<boxGeometry args={[1.9, 2.5, 0.14]} />
|
| 850 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 851 |
+
</mesh>
|
| 852 |
+
<mesh position={[0, 0.12, 0.07]}>
|
| 853 |
+
<planeGeometry args={[1.62, 2.1]} />
|
| 854 |
+
<meshBasicMaterial map={textures && textures[key] ? textures[key] : null}
|
| 855 |
+
color={textures && textures[key] ? '#ffffff' : '#5a4636'} transparent opacity={0.96} toneMapped={false} fog={false} />
|
| 856 |
+
</mesh>
|
| 857 |
+
<mesh position={[0, -1.18, 0.07]}>
|
| 858 |
+
<planeGeometry args={[1.62, 0.34]} />
|
| 859 |
+
<meshBasicMaterial color="#120b08" transparent opacity={0.85} toneMapped={false} fog={false} />
|
| 860 |
+
</mesh>
|
| 861 |
+
</group>
|
| 862 |
+
))}
|
| 863 |
+
</group>
|
| 864 |
+
)
|
| 865 |
+
}
|
| 866 |
+
|
| 867 |
+
/* =========================================================================
|
| 868 |
+
HỘI TRƯỜNG ĐÁ sign + hình nộm rage + sân kéo co/giữ bóng trên sàn
|
| 869 |
+
========================================================================= */
|
| 870 |
+
function BenchSign() {
|
| 871 |
+
const [tex, setTex] = React.useState(null)
|
| 872 |
+
React.useEffect(() => {
|
| 873 |
+
let alive = true
|
| 874 |
+
const c = document.createElement('canvas'); c.width = 900; c.height = 200
|
| 875 |
+
const g = c.getContext('2d')
|
| 876 |
+
const gr = g.createLinearGradient(0, 0, 0, 200)
|
| 877 |
+
gr.addColorStop(0, '#8e1b2a'); gr.addColorStop(0.5, '#6a1220'); gr.addColorStop(1, '#3a0a12')
|
| 878 |
+
g.fillStyle = gr; g.fillRect(0, 0, 900, 200)
|
| 879 |
+
g.strokeStyle = 'rgba(236,208,140,0.8)'; g.lineWidth = 6; g.strokeRect(8, 8, 884, 184)
|
| 880 |
+
g.textAlign = 'center'; g.fillStyle = '#ffd76a'; g.font = '800 44px Bitter, Georgia, serif'
|
| 881 |
+
g.fillText('🎙 PHÒNG NÓI CHUYỆN', 450, 90)
|
| 882 |
+
g.fillStyle = '#fff2c8'; g.font = '900 84px Bitter, Georgia, serif'
|
| 883 |
+
g.fillText('Hội trường đá', 450, 160)
|
| 884 |
+
const tx = new THREE.CanvasTexture(c); tx.colorSpace = THREE.SRGBColorSpace; tx.anisotropy = 16
|
| 885 |
+
if (alive) setTex(tx)
|
| 886 |
+
return () => { alive = false }
|
| 887 |
+
}, [])
|
| 888 |
+
if (!tex) return null
|
| 889 |
+
return (
|
| 890 |
+
<mesh position={[0, 8.6, -27.2]} rotation={[0.15, 0, 0]}>
|
| 891 |
+
<planeGeometry args={[6.4, 1.5]} />
|
| 892 |
+
<meshBasicMaterial map={tex} transparent toneMapped={false} fog={false} />
|
| 893 |
+
</mesh>
|
| 894 |
+
)
|
| 895 |
+
}
|
| 896 |
+
function RageDummies() {
|
| 897 |
+
const colors = ['#c9a27a', '#b8102a', '#4a4a4a', '#f0c078']
|
| 898 |
+
return (
|
| 899 |
+
<group>
|
| 900 |
+
{colors.map((col, i) => {
|
| 901 |
+
const a = 1.9 + i * 1.2
|
| 902 |
+
const x = Math.sin(a) * 11.5 + (i % 2 ? 1.5 : -1.5)
|
| 903 |
+
const z = Math.cos(a) * 11.5
|
| 904 |
+
const fy = floorHeight(Math.hypot(x, z))
|
| 905 |
+
return (
|
| 906 |
+
<group key={i} position={[x, fy, z]} rotation-y={-a + 1.6}>
|
| 907 |
+
<mesh position={[0, 0.85, 0]}>
|
| 908 |
+
<capsuleGeometry args={[0.32, 1.1, 6, 10]} />
|
| 909 |
+
<meshLambertMaterial color={col} flatShading />
|
| 910 |
+
</mesh>
|
| 911 |
+
<mesh position={[0, 2.05, 0]}>
|
| 912 |
+
<sphereGeometry args={[0.34, 10, 8]} />
|
| 913 |
+
<meshLambertMaterial color="#ffd9c8" />
|
| 914 |
+
</mesh>
|
| 915 |
+
<mesh position={[0, 2.15, 0]} rotation-x={0.35}>
|
| 916 |
+
<coneGeometry args={[0.3, 0.4, 6]} />
|
| 917 |
+
<meshLambertMaterial color={col} flatShading />
|
| 918 |
+
</mesh>
|
| 919 |
+
<mesh position={[0, 0.5, 0]}>
|
| 920 |
+
<cylinderGeometry args={[0.22, 0.26, 0.12, 8]} />
|
| 921 |
+
<meshLambertMaterial color="#3a2a18" flatShading />
|
| 922 |
+
</mesh>
|
| 923 |
+
</group>
|
| 924 |
+
)
|
| 925 |
+
})}
|
| 926 |
+
</group>
|
| 927 |
+
)
|
| 928 |
+
}
|
| 929 |
+
function CourtMarks() {
|
| 930 |
+
return (
|
| 931 |
+
<group>
|
| 932 |
+
{[[-18, -35, 0.35], [18, -35, -0.35]].map(([x, z, rot], i) => (
|
| 933 |
+
<group key={i} position={[x, 0.03, z]} rotation={[0, rot, 0]}>
|
| 934 |
+
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
| 935 |
+
<planeGeometry args={[10, 8]} />
|
| 936 |
+
<meshBasicMaterial color="#1c1612" transparent opacity={0.55} />
|
| 937 |
+
</mesh>
|
| 938 |
+
<mesh position={[0, 0.01, -4]} rotation={[-Math.PI / 2, 0, 0]}>
|
| 939 |
+
<planeGeometry args={[0.25, 0.4]} />
|
| 940 |
+
<meshBasicMaterial color="#ffd76a" />
|
| 941 |
+
</mesh>
|
| 942 |
+
<mesh position={[0, 0.01, 4]} rotation={[-Math.PI / 2, 0, 0]}>
|
| 943 |
+
<planeGeometry args={[0.25, 0.4]} />
|
| 944 |
+
<meshBasicMaterial color="#ffd76a" />
|
| 945 |
+
</mesh>
|
| 946 |
+
</group>
|
| 947 |
+
))}
|
| 948 |
+
<group position={[0, 0.03, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
| 949 |
+
<mesh>
|
| 950 |
+
<ringGeometry args={[11, 15, 48]} />
|
| 951 |
+
<meshBasicMaterial color="#1c1612" transparent opacity={0.45} />
|
| 952 |
+
</mesh>
|
| 953 |
+
<mesh position={[0, 0.01, -13]}>
|
| 954 |
+
<circleGeometry args={[1.9, 20]} />
|
| 955 |
+
<meshBasicMaterial color="#ffd76a" />
|
| 956 |
+
</mesh>
|
| 957 |
+
</group>
|
| 958 |
+
</group>
|
| 959 |
+
)
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
/* =========================================================================
|
| 963 |
+
CÚP VÀNG TRUNG TÂM (trophy)
|
| 964 |
+
========================================================================= */
|
| 965 |
+
function Trophy() {
|
| 966 |
+
return (
|
| 967 |
+
<group position={[0, 0, 0]} rotation-y={0.5}>
|
| 968 |
+
<mesh position={[0, 0.12, 0]} castShadow>
|
| 969 |
+
<cylinderGeometry args={[0.85, 1.0, 0.24, 8]} />
|
| 970 |
+
<meshStandardMaterial color="#5a4a2c" metalness={0.6} roughness={0.4} flatShading />
|
| 971 |
+
</mesh>
|
| 972 |
+
<mesh position={[0, 0.34, 0]} castShadow>
|
| 973 |
+
<cylinderGeometry args={[0.55, 0.7, 0.2, 8]} />
|
| 974 |
+
<meshStandardMaterial color="#8a6a2c" metalness={0.7} roughness={0.35} flatShading />
|
| 975 |
+
</mesh>
|
| 976 |
+
<mesh position={[0, 0.85, 0]} castShadow>
|
| 977 |
+
<cylinderGeometry args={[0.42, 0.5, 0.85, 10]} />
|
| 978 |
+
<meshStandardMaterial color="#c9a24e" metalness={0.85} roughness={0.25} flatShading />
|
| 979 |
+
</mesh>
|
| 980 |
+
<mesh position={[0, 1.32, 0]} castShadow>
|
| 981 |
+
<torusGeometry args={[0.5, 0.09, 8, 18]} />
|
| 982 |
+
<meshStandardMaterial color="#c9a24e" metalness={0.85} roughness={0.25} flatShading />
|
| 983 |
+
</mesh>
|
| 984 |
+
<mesh position={[-0.42, 0.85, 0]} rotation-z={0.3}>
|
| 985 |
+
<torusGeometry args={[0.26, 0.07, 8, 14, Math.PI]} />
|
| 986 |
+
<meshStandardMaterial color="#c9a24e" metalness={0.85} roughness={0.25} flatShading />
|
| 987 |
+
</mesh>
|
| 988 |
+
<mesh position={[0.42, 0.85, 0]} rotation-z={-0.3}>
|
| 989 |
+
<torusGeometry args={[0.26, 0.07, 8, 14, Math.PI]} />
|
| 990 |
+
<meshStandardMaterial color="#c9a24e" metalness={0.85} roughness={0.25} flatShading />
|
| 991 |
+
</mesh>
|
| 992 |
+
<mesh position={[0, 1.62, 0]} castShadow>
|
| 993 |
+
<sphereGeometry args={[0.2, 14, 14]} />
|
| 994 |
+
<meshStandardMaterial color="#c9a24e" metalness={0.85} roughness={0.25} flatShading />
|
| 995 |
+
</mesh>
|
| 996 |
+
<pointLight position={[0, 2.6, 0]} color="#ffd76a" intensity={18} distance={9} decay={1.6} />
|
| 997 |
+
</group>
|
| 998 |
+
)
|
| 999 |
+
}
|
| 1000 |
+
|
| 1001 |
+
/* =========================================================================
|
| 1002 |
+
BANNER MU TRÊN VÒM
|
| 1003 |
+
========================================================================= */
|
| 1004 |
+
function makeBannerTex(text) {
|
| 1005 |
+
const c = document.createElement('canvas')
|
| 1006 |
+
c.width = 512; c.height = 128
|
| 1007 |
+
const g = c.getContext('2d')
|
| 1008 |
+
g.fillStyle = '#c8102e'; g.fillRect(0, 0, 512, 128)
|
| 1009 |
+
g.fillStyle = '#8f0f22'; g.fillRect(6, 6, 500, 116)
|
| 1010 |
+
g.strokeStyle = '#ffd76a'; g.lineWidth = 4; g.strokeRect(6, 6, 500, 116)
|
| 1011 |
+
g.fillStyle = '#ffffff'; g.font = '900 52px "Be Vietnam Pro", sans-serif'
|
| 1012 |
+
g.textAlign = 'center'; g.textBaseline = 'middle'
|
| 1013 |
+
g.fillText(text, 256, 66)
|
| 1014 |
+
const t = new THREE.CanvasTexture(c)
|
| 1015 |
+
t.colorSpace = THREE.SRGBColorSpace
|
| 1016 |
+
return t
|
| 1017 |
+
}
|
| 1018 |
+
function CaveBanners() {
|
| 1019 |
+
const texA = useMemo(() => makeBannerTex('GLORY GLORY'), [])
|
| 1020 |
+
const texB = useMemo(() => makeBannerTex('MAN UNITED'), [])
|
| 1021 |
+
const texC = useMemo(() => makeBannerTex('MU FAN'), [])
|
| 1022 |
+
const conf = [
|
| 1023 |
+
{ tex: texA, pos: [-14, 12.5, -24], rot: [0, 0.5, 0] },
|
| 1024 |
+
{ tex: texB, pos: [16, 12.8, -30], rot: [0, -0.45, 0] },
|
| 1025 |
+
{ tex: texC, pos: [0, 13.2, -8], rot: [0, 0, 0] },
|
| 1026 |
+
]
|
| 1027 |
+
return (
|
| 1028 |
+
<group>
|
| 1029 |
+
{conf.map((c, i) => (
|
| 1030 |
+
<group key={i} position={c.pos} rotation={c.rot}>
|
| 1031 |
+
<mesh>
|
| 1032 |
+
<planeGeometry args={[6, 1.5]} />
|
| 1033 |
+
<meshBasicMaterial map={c.tex} transparent toneMapped={false} fog={false} />
|
| 1034 |
+
</mesh>
|
| 1035 |
+
</group>
|
| 1036 |
+
))}
|
| 1037 |
+
</group>
|
| 1038 |
+
)
|
| 1039 |
+
}
|
| 1040 |
+
|
| 1041 |
+
/* =========================================================================
|
| 1042 |
+
ÁO ĐỎ MU TRƯNG BÀY — jersey #7 trong khung gỗ cạnh màn hình lớn
|
| 1043 |
+
========================================================================= */
|
| 1044 |
+
function _jerseyTex() {
|
| 1045 |
+
if (typeof document === 'undefined') return null
|
| 1046 |
+
const c = document.createElement('canvas'); c.width = 512; c.height = 640
|
| 1047 |
+
const g = c.getContext('2d')
|
| 1048 |
+
g.fillStyle = '#17120d'; g.fillRect(0, 0, 512, 640)
|
| 1049 |
+
g.fillStyle = '#241b12'; g.fillRect(26, 26, 460, 588)
|
| 1050 |
+
g.fillStyle = '#c8102e'
|
| 1051 |
+
g.beginPath(); g.moveTo(138, 168); g.lineTo(374, 168); g.lineTo(374, 468); g.lineTo(138, 468); g.closePath(); g.fill()
|
| 1052 |
+
g.beginPath(); g.moveTo(138, 168); g.lineTo(50, 118); g.lineTo(86, 332); g.lineTo(138, 298); g.closePath(); g.fill()
|
| 1053 |
+
g.beginPath(); g.moveTo(374, 168); g.lineTo(462, 118); g.lineTo(426, 332); g.lineTo(374, 298); g.closePath(); g.fill()
|
| 1054 |
+
g.fillStyle = '#7c0d20'
|
| 1055 |
+
g.beginPath(); g.arc(256, 172, 36, 0, Math.PI * 2); g.fill()
|
| 1056 |
+
g.fillStyle = '#ffffff'
|
| 1057 |
+
g.font = '800 200px Georgia, serif'; g.textAlign = 'center'; g.textBaseline = 'middle'
|
| 1058 |
+
g.fillText('7', 256, 368)
|
| 1059 |
+
g.fillStyle = '#ffe9c0'; g.font = '700 40px Georgia, serif'
|
| 1060 |
+
g.fillText('MANCHESTER UNITED', 256, 548)
|
| 1061 |
+
const t = new THREE.CanvasTexture(c)
|
| 1062 |
+
t.colorSpace = THREE.SRGBColorSpace
|
| 1063 |
+
t.anisotropy = 16
|
| 1064 |
+
return t
|
| 1065 |
+
}
|
| 1066 |
+
function JerseyDisplay() {
|
| 1067 |
+
const [tex, setTex] = React.useState(null)
|
| 1068 |
+
React.useEffect(() => {
|
| 1069 |
+
let alive = true
|
| 1070 |
+
const t = _jerseyTex()
|
| 1071 |
+
if (alive && t) setTex(t)
|
| 1072 |
+
return () => { alive = false }
|
| 1073 |
+
}, [])
|
| 1074 |
+
return (
|
| 1075 |
+
<group position={[15.5, 0, -41]} rotation={[0, -0.42, 0]}>
|
| 1076 |
+
<mesh position={[0, 0.425, -0.12]}>
|
| 1077 |
+
<boxGeometry args={[0.55, 0.85, 0.55]} />
|
| 1078 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 1079 |
+
</mesh>
|
| 1080 |
+
<mesh position={[0, 2.15, -0.1]}>
|
| 1081 |
+
<boxGeometry args={[2.7, 3.55, 0.16]} />
|
| 1082 |
+
<meshLambertMaterial color="#5a3b22" flatShading />
|
| 1083 |
+
</mesh>
|
| 1084 |
+
{tex && (
|
| 1085 |
+
<mesh position={[0, 2.18, 0.04]}>
|
| 1086 |
+
<planeGeometry args={[2.35, 2.94]} />
|
| 1087 |
+
<meshBasicMaterial map={tex} transparent fog={false} toneMapped={false} />
|
| 1088 |
+
</mesh>
|
| 1089 |
+
)}
|
| 1090 |
+
</group>
|
| 1091 |
+
)
|
| 1092 |
+
}
|
| 1093 |
+
|
| 1094 |
+
/* =========================================================================
|
| 1095 |
+
SÂN NGOÀI CỬA HANG (+z) — sân, cây, đá, trời, nắng
|
| 1096 |
+
========================================================================= */
|
| 1097 |
+
function _makeSkyTex() {
|
| 1098 |
+
if (typeof document === 'undefined') return null
|
| 1099 |
+
const c = document.createElement('canvas'); c.width = 64; c.height = 256
|
| 1100 |
+
const g = c.getContext('2d')
|
| 1101 |
+
const gr = g.createLinearGradient(0, 0, 0, 256)
|
| 1102 |
+
gr.addColorStop(0, '#7fb2e0'); gr.addColorStop(0.55, '#cfe3f2'); gr.addColorStop(0.8, '#e8d9bd'); gr.addColorStop(1, '#d8c2a0')
|
| 1103 |
+
g.fillStyle = gr; g.fillRect(0, 0, 64, 256)
|
| 1104 |
+
const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace
|
| 1105 |
+
return t
|
| 1106 |
+
}
|
| 1107 |
+
const _skyTexC = _makeSkyTex()
|
| 1108 |
+
function SkyDome() {
|
| 1109 |
+
const tex = useMemo(() => _skyTexC, [])
|
| 1110 |
+
return (
|
| 1111 |
+
<mesh position={[0, 20, 178]} rotation={[0, Math.PI, 0]} fog={false}>
|
| 1112 |
+
<planeGeometry args={[170, 96]} />
|
| 1113 |
+
<meshBasicMaterial map={tex} fog={false} toneMapped={false} depthWrite={false} />
|
| 1114 |
+
</mesh>
|
| 1115 |
+
)
|
| 1116 |
+
}
|
| 1117 |
+
function OutdoorGround() {
|
| 1118 |
+
const geo = useMemo(() => {
|
| 1119 |
+
const m = new THREE.PlaneGeometry(170, 100, 70, 40)
|
| 1120 |
+
m.rotateX(-Math.PI / 2)
|
| 1121 |
+
const p = m.attributes.position
|
| 1122 |
+
const cols = new Float32Array(p.count * 3)
|
| 1123 |
+
const cA = new THREE.Color('#8a7a5c'), cB = new THREE.Color('#6d5c42'), moss = new THREE.Color('#7d7a45'), tmp = new THREE.Color()
|
| 1124 |
+
for (let i = 0; i < p.count; i++) {
|
| 1125 |
+
const x = p.getX(i), z = p.getZ(i)
|
| 1126 |
+
p.setY(i, fbm3(x * 0.06, z * 0.06, 0, 21) * 1.1 - 0.3)
|
| 1127 |
+
tmp.copy(cA).lerp(cB, fbm3(x * 0.12, z * 0.12, 3, 22) * 0.5 + 0.25)
|
| 1128 |
+
tmp.lerp(moss, Math.max(0, fbm3(x * 0.08, 9, z * 0.08, 23)) * 0.35)
|
| 1129 |
+
cols[i * 3] = tmp.r; cols[i * 3 + 1] = tmp.g; cols[i * 3 + 2] = tmp.b
|
| 1130 |
+
}
|
| 1131 |
+
m.setAttribute('color', new THREE.BufferAttribute(cols, 3))
|
| 1132 |
+
m.computeVertexNormals()
|
| 1133 |
+
return m
|
| 1134 |
+
}, [])
|
| 1135 |
+
return (
|
| 1136 |
+
<mesh geometry={geo} position={[0, 0, 126]} receiveShadow fog={false}>
|
| 1137 |
+
<meshLambertMaterial vertexColors fog={false} />
|
| 1138 |
+
</mesh>
|
| 1139 |
+
)
|
| 1140 |
+
}
|
| 1141 |
+
function OutdoorPath() {
|
| 1142 |
+
return (
|
| 1143 |
+
<mesh position={[0, 0.04, 112]} rotation={[-Math.PI / 2, 0, 0]} fog={false}>
|
| 1144 |
+
<planeGeometry args={[11, 66]} />
|
| 1145 |
+
<meshBasicMaterial color="#a89474" fog={false} transparent opacity={0.85} />
|
| 1146 |
+
</mesh>
|
| 1147 |
+
)
|
| 1148 |
+
}
|
| 1149 |
+
function OutdoorProps() {
|
| 1150 |
+
const items = useMemo(() => {
|
| 1151 |
+
const out = []
|
| 1152 |
+
const boulder = (x, z, s, seed) => {
|
| 1153 |
+
const geo = displaceAndColor(new THREE.IcosahedronGeometry(1, 1),
|
| 1154 |
+
{ amp: 0.3, freq: 1.1, seed, base: '#9a8a6c', shade: '#77664c', dark: '#4c3f2e', warm: '#b3a284' })
|
| 1155 |
+
out.push({ kind: 'boulder', geo, x, z, s })
|
| 1156 |
+
}
|
| 1157 |
+
const tree = (x, z, s, seed) => out.push({ kind: 'tree', x, z, s, seed })
|
| 1158 |
+
boulder(-8, 80, 1.6, 101); boulder(9, 82, 2.1, 102); boulder(-14, 86, 2.8, 103)
|
| 1159 |
+
boulder(15, 88, 2.4, 104); boulder(-6, 94, 1.2, 105); boulder(11, 96, 1.4, 106)
|
| 1160 |
+
boulder(-18, 100, 3.2, 107); boulder(19, 102, 3.0, 108)
|
| 1161 |
+
tree(-12, 88, 1.5, 201); tree(12, 90, 1.8, 202); tree(-16, 96, 2.1, 203); tree(16, 98, 2.0, 204)
|
| 1162 |
+
tree(-8, 104, 1.4, 205); tree(8, 108, 1.6, 206)
|
| 1163 |
+
return out
|
| 1164 |
+
}, [])
|
| 1165 |
+
return (
|
| 1166 |
+
<group>
|
| 1167 |
+
{items.map((it, i) => it.kind === 'boulder' ? (
|
| 1168 |
+
<mesh key={i} geometry={it.geo} position={[it.x, it.s * 0.35, it.z]} scale={[it.s, it.s * 0.85, it.s]} fog={false}>
|
| 1169 |
+
<meshLambertMaterial vertexColors flatShading fog={false} />
|
| 1170 |
+
</mesh>
|
| 1171 |
+
) : (
|
| 1172 |
+
<group key={i} position={[it.x, 0, it.z]} scale={[it.s, it.s, it.s]} fog={false}>
|
| 1173 |
+
<mesh position={[0, 1.1, 0]} fog={false}>
|
| 1174 |
+
<cylinderGeometry args={[0.22, 0.3, 2.2, 6]} />
|
| 1175 |
+
<meshLambertMaterial color="#6b4a2c" flatShading fog={false} />
|
| 1176 |
+
</mesh>
|
| 1177 |
+
{[0, 1, 2].map((k) => (
|
| 1178 |
+
<mesh key={k} position={[Math.sin(k * 2.1) * 0.7, 2.6 + k * 0.55, Math.cos(k * 2.1) * 0.7]} fog={false}>
|
| 1179 |
+
<coneGeometry args={[1.35 - k * 0.25, 1.6, 7]} />
|
| 1180 |
+
<meshLambertMaterial color={k === 0 ? '#3f6b2e' : k === 1 ? '#4c7a38' : '#5a8a42'} flatShading fog={false} />
|
| 1181 |
+
</mesh>
|
| 1182 |
+
))}
|
| 1183 |
+
</group>
|
| 1184 |
+
))}
|
| 1185 |
+
</group>
|
| 1186 |
+
)
|
| 1187 |
+
}
|
| 1188 |
+
function OutdoorLight() {
|
| 1189 |
+
const tex = useMemo(() => {
|
| 1190 |
+
const c = document.createElement('canvas'); c.width = c.height = 128
|
| 1191 |
+
const g = c.getContext('2d')
|
| 1192 |
+
const gr = g.createRadialGradient(64, 64, 2, 64, 64, 62)
|
| 1193 |
+
gr.addColorStop(0, 'rgba(255,240,200,0.9)'); gr.addColorStop(0.5, 'rgba(255,214,140,0.35)'); gr.addColorStop(1, 'rgba(255,214,140,0)')
|
| 1194 |
+
g.fillStyle = gr; g.fillRect(0, 0, 128, 128)
|
| 1195 |
+
const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace
|
| 1196 |
+
return t
|
| 1197 |
+
}, [])
|
| 1198 |
+
return (
|
| 1199 |
+
<>
|
| 1200 |
+
<directionalLight position={[15, 30, 140]} intensity={1.6} color="#fff1d6" fog={false} />
|
| 1201 |
+
<pointLight position={[0, 16, 118]} color="#ffe6c0" intensity={5} distance={70} decay={0.5} fog={false} />
|
| 1202 |
+
<sprite position={[0, 34, 132]} scale={[34, 34, 1]} fog={false}>
|
| 1203 |
+
<spriteMaterial map={tex} transparent opacity={0.9} fog={false} depthWrite={false} blending={THREE.AdditiveBlending} />
|
| 1204 |
+
</sprite>
|
| 1205 |
+
</>
|
| 1206 |
+
)
|
| 1207 |
+
}
|
| 1208 |
+
function OutdoorScene() {
|
| 1209 |
+
return (
|
| 1210 |
+
<group>
|
| 1211 |
+
<SkyDome />
|
| 1212 |
+
<OutdoorGround />
|
| 1213 |
+
<OutdoorPath />
|
| 1214 |
+
<OutdoorProps />
|
| 1215 |
+
<OutdoorLight />
|
| 1216 |
+
</group>
|
| 1217 |
+
)
|
| 1218 |
+
}
|
| 1219 |
|
| 1220 |
/* =========================================================================
|
| 1221 |
TỔNG HỢP CaveScene
|
|
|
|
| 1234 |
<Board />
|
| 1235 |
<LegendRing />
|
| 1236 |
<Benches />
|
| 1237 |
+
<LegendWall />
|
| 1238 |
+
<LegendPortraits />
|
| 1239 |
+
<EplBoard />
|
| 1240 |
+
<OnThisDayBoard />
|
| 1241 |
+
<BenchSign />
|
| 1242 |
+
<RageDummies />
|
| 1243 |
+
<CourtMarks />
|
| 1244 |
+
<Trophy />
|
| 1245 |
+
<CaveBanners />
|
| 1246 |
+
<JerseyDisplay />
|
| 1247 |
<Entrance />
|
| 1248 |
+
<OutdoorScene />
|
| 1249 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />
|
| 1250 |
<ambientLight intensity={0.7} color={0xf4cba0} />
|
| 1251 |
</>
|
| 1252 |
)
|
| 1253 |
}
|
| 1254 |
+
|
| 1255 |
+
export { floorHeight, domeHeight }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|