feat: update scene configuration and image handling for Chùa Thái Lạc tour

This commit is contained in:
tuyenio
2026-06-24 12:46:24 +07:00
parent 2820724ace
commit 035411493d
+185 -83
View File
@@ -4,26 +4,35 @@
//
// Ảnh panorama đặt tại public/images/chua-thai-lac/01.jpg … 26.jpg
// (đặt tên ASCII theo thứ tự cảnh để tránh lỗi mã hóa Unicode/đường dẫn khi triển khai).
import type { TourConfig, TourScene, InfoMarker } from "@/app/components/VirtualTour";
import type { TourConfig, TourScene, InfoMarker, Hotspot } from "@/app/components/VirtualTour";
const base = "/images/chua-thai-lac";
// Ảnh theo số thứ tự cảnh: scene-1 → 01.jpg, scene-2 → 02.jpg, …
const imageFor = (index: number) => `${base}/${String(index + 1).padStart(2, "0")}.jpg`;
// Trỏ tới đúng file ảnh gốc theo SỐ FILE (01..26), độc lập với thứ tự cảnh sau khi
// đánh số lại — nhờ vậy có thể bỏ cảnh giữa chừng mà không phải đổi tên ảnh.
const imageForFile = (fileNumber: number) => `${base}/${String(fileNumber).padStart(2, "0")}.jpg`;
type SceneSeed = {
title: string;
location: string;
// Số thứ tự FILE ảnh gốc trên đĩa (01..26). Không nhất thiết trùng số cảnh hiển thị.
imageFile: number;
initialYaw?: number;
infoMarkers?: InfoMarker[];
};
// Tuyến tham quan: cổng → đường/vòm → tiền đường & chính điện → khu thờ → ban tượng → cảnh quan.
// Thứ tự mảng = thứ tự ảnh (01..26). yaw/pitch hotspot để mặc định (tiến: 0, lùi: 180);
// tinh chỉnh trực quan khi chạy dev.
// ─────────────────────────────────────────────────────────────────────────────
// Danh sách cảnh sau khi XOÁ 3 cảnh (ảnh 20/21/22) và ĐÁNH SỐ LẠI liền mạch 1..23.
// Mỗi cảnh giữ nguyên ảnh gốc qua `imageFile`; số cảnh hiển thị = vị trí trong mảng.
// Tên cảnh & nhóm khu vực được đặt lại theo đúng nội dung từng ảnh panorama:
// 1-4 Khu cổng · 5-7 Cảnh quan vườn · 8-11 Sân chùa & Tam quan ·
// 12-17 Khu nhà Tổ & nhà khách · 18-23 Điện thờ & hành lang tượng.
// ─────────────────────────────────────────────────────────────────────────────
const seeds: SceneSeed[] = [
// 1 — ảnh 01: cổng chùa, cây cổ thụ và ghế đá trong sân
{
title: "Chính diện cổng chùa",
title: "Cổng chùa Thái Lạc",
location: "Khu cổng",
imageFile: 1,
infoMarkers: [
{
id: "gioi-thieu",
@@ -39,18 +48,43 @@ const seeds: SceneSeed[] = [
},
],
},
{ title: "Cổng bên phải", location: "Khu cổng" },
{ title: "Cổng bên trái", location: "Khu cổng" },
{ title: "Lối vào bên trái", location: "Khu cổng" },
{ title: "Đường bên phải", location: "Đường vào" },
{ title: "Đường bên trái qua vòm", location: "Đường vào" },
{ title: "Đường tả qua vòm", location: "Đường vào" },
{ title: "Chính sảnh", location: "Tiền đường & Chính điện" },
{ title: "Gian giữa", location: "Tiền đường & Chính điện" },
{ title: "Trung tâm chính điện", location: "Tiền đường & Chính điện" },
// 2 — ảnh 02: cổng chùa nhìn từ phía đường nhựa
{ title: "Cổng chùa bên đường", location: "Khu cổng", imageFile: 2 },
// 3 — ảnh 03: đường trước cổng, cột cờ
{ title: "Đường vào cổng chùa", location: "Khu cổng", imageFile: 3 },
// 4 — ảnh 04: cổng vòm nhỏ dẫn vào sân chùa
{ title: "Cổng vào khuôn viên", location: "Khu cổng", imageFile: 4 },
// 5 — ảnh 05: sân vườn, ghế đá, bảng nội quy
{ title: "Vườn chùa", location: "Cảnh quan vườn chùa", imageFile: 5 },
// 6 — ảnh 06: lối đi rợp bóng hàng cây, tường vàng
{ title: "Lối vào rợp bóng cây", location: "Cảnh quan vườn chùa", imageFile: 6 },
// 7 — ảnh 07: hồ nước cảnh quan, lan can đá, gò non bộ
{ title: "Hồ nước cảnh quan", location: "Cảnh quan vườn chùa", imageFile: 7 },
// 8 — ảnh 08: sân lớn lát đá trước gác chuông tam quan đồ sộ
{ title: "Gác chuông Tam quan", location: "Sân chùa & Tam quan", imageFile: 8 },
// 9 — ảnh 09: sân gạch, tam quan bên trái, đài Quan Âm bên phải
{ title: "Sân chùa và đài Quan Âm", location: "Sân chùa & Tam quan", imageFile: 9 },
// 10 — ảnh 10: đài Quan Âm lộ thiên, sân lát gạch đỏ
{ title: "Đài Quan Âm lộ thiên", location: "Sân chùa & Tam quan", imageFile: 10 },
// 11 — ảnh 11: vườn tháp, tháp mộ gạch cổ
{ title: "Vườn tháp cổ", location: "Sân chùa & Tam quan", imageFile: 11 },
// 12 — ảnh 12: lối đi hẹp giữa các dãy nhà, chậu cảnh
{ title: "Lối vào khu nhà Tổ", location: "Khu nhà Tổ & nhà khách", imageFile: 12 },
// 13 — ảnh 13: sân trong khu nhà ngang, cây cổ thụ
{ title: "Sân nhà Tổ", location: "Khu nhà Tổ & nhà khách", imageFile: 13 },
// 14 — ảnh 14: hiên nhà có hàng cột, giàn cây xanh che mát
{ title: "Hiên nhà dưới giàn cây", location: "Khu nhà Tổ & nhà khách", imageFile: 14 },
// 15 — ảnh 15: sân sau, hiên gạch đỏ của dãy nhà
{ title: "Sân sau nhà khách", location: "Khu nhà Tổ & nhà khách", imageFile: 15 },
// 16 — ảnh 16: sân trong giếng trời, cây cổ thụ tỏa bóng, bàn ghế đá
{ title: "Sân trong giếng trời", location: "Khu nhà Tổ & nhà khách", imageFile: 16 },
// 17 — ảnh 17: nhà khách, hàng ghế gỗ dài, tường treo ảnh
{ title: "Nhà khách", location: "Khu nhà Tổ & nhà khách", imageFile: 17 },
// 18 — ảnh 18: hành lang gỗ cổ với hàng tượng, cột & vì kèo cổ (chạm khắc thời Trần)
{
title: "Chính điện",
location: "Tiền đường & Chính điện",
title: "Hành lang tượng cổ",
location: "Điện thờ & hành lang tượng",
imageFile: 18,
infoMarkers: [
{
id: "cham-khac-go",
@@ -66,83 +100,151 @@ const seeds: SceneSeed[] = [
},
],
},
{ title: "Gian trong chính điện", location: "Tiền đường & Chính điện" },
{ title: "Ban thờ bên trái", location: "Khu thờ trong chùa" },
{ title: "Ban thờ chính giữa", location: "Khu thờ trong chùa" },
{ title: "Ban thờ bên phải", location: "Khu thờ trong chùa" },
{ title: "Gian trong - giữa", location: "Khu thờ trong chùa" },
{ title: "Gian trong bên trái", location: "Khu thờ trong chùa" },
{ title: "Hành lang bên phải", location: "Khu thờ trong chùa" },
{ title: "Hành lang bên phải (tiếp)", location: "Khu thờ trong chùa" },
{
title: "Tượng Quan Âm",
location: "Ban thờ tượng",
infoMarkers: [
{
id: "dai-quan-am",
title: "Đài Quan Âm",
eyebrow: "Không gian thờ tự",
yaw: 0,
pitch: -2,
iconType: "envelope",
content: [
"Khu vực tôn trí tượng Quan Âm Bồ Tát cùng các pho tượng La Hán, Phật, là không gian chiêm bái trang nghiêm trong khuôn viên chùa.",
],
},
],
},
{ title: "Tượng La Hán bên trái", location: "Ban thờ tượng" },
{ title: "Nhà thờ Phật bên phải", location: "Ban thờ tượng" },
{ title: "Ao cá Koi", location: "Cảnh quan" },
{ title: "Gốc ngõa bên trái", location: "Cảnh quan" },
{ title: "Gốc ngõa bên phải", location: "Cảnh quan" },
{ title: "Phía sau chùa", location: "Cảnh quan" },
// 19 — ảnh 19: sân thiên tỉnh, hòm công đức, hòn non bộ
{ title: "n thiên tỉnh", location: "Điện thờ & hành lang tượng", imageFile: 19 },
// 20 — ảnh 23: hàng tượng thờ bên hành lang, sân vườn cây lựu
{ title: "Hành lang tượng và sân vườn", location: "Điện thờ & hành lang tượng", imageFile: 23 },
// 21 — ảnh 24: gian thờ bên trong, vách gỗ bức bàn, ban thờ hai bên
{ title: "Gian thờ bên trong", location: "Điện thờ & hành lang tượng", imageFile: 24 },
// 22 — ảnh 25: thượng điện, khám thờ gỗ, cột cổ, ban thờ hai bên
{ title: "Thượng điện", location: "Điện thờ & hành lang tượng", imageFile: 25 },
// 23 — ảnh 26: hậu đường, hàng cột, ban thờ tượng dọc tường
{ title: "Hậu đường", location: "Điện thờ & hành lang tượng", imageFile: 26 },
];
const sceneId = (index: number) => `scene-${index + 1}`;
// ─────────────────────────────────────────────────────────────────────────────
// ĐỒ THỊ HƯỚNG ĐI (có hướng) — khoá là SỐ CẢNH MỚI (1..23), giá trị là danh sách
// các cảnh mà từ đó có thể đi tới. Đây chính là các mũi tên hiển thị trong ảnh 360.
// Lấy theo yêu cầu (đã quy đổi số cũ → số mới: 23→20, 24→21, 25→22, 26→23):
// cũ 8→…,25 ⇒ 8→…,22 | cũ 19→…,23 ⇒ 19→…,20 | cũ 23→19,(24),25 ⇒ 20→19,21,22
// cũ 25→23,26,8 ⇒ 22→20,23,8 | cũ 24→23 ⇒ 21→20 | thêm 23→22 để có lối quay lại.
// ─────────────────────────────────────────────────────────────────────────────
const adjacency: Record<number, number[]> = {
1: [2, 3, 8],
2: [1, 9],
3: [1, 4],
4: [5, 7],
5: [4, 6, 7, 8],
6: [5, 17],
7: [4, 5],
8: [1, 5, 9, 22],
9: [2, 8, 10, 12],
10: [9, 11],
11: [9, 10],
12: [9, 13],
13: [12, 14],
14: [13, 15],
15: [14, 16],
16: [15, 17],
17: [16, 19, 6],
18: [17, 19],
19: [17, 18, 20],
20: [19, 21],
21: [20, 22],
22: [21, 23, 8],
23: [22],
};
// Toạ độ sơ đồ (mini-map): x trái→phải, y trên(0)→dưới(100).
// Cổng ở dưới, tiến dần lên trên qua chính điện → khu thờ → cảnh quan sau chùa.
// Bố cục này quyết định luôn HƯỚNG mũi tên trong ảnh 360 (tính theo phương vị bản đồ).
// Ràng buộc hình học:
// • 1-2-9-8 là một hình vuông (2,9 bên trái), 1-3-5-8 là một hình vuông (3,5 bên phải);
// hai hình vuông dùng chung cạnh đứng 1-8 ở giữa (x=50).
// • 3,4,5,6 nằm trên một cột dọc bên phải (x=68); 3-4-5 thẳng hàng tạo cạnh phải hình vuông.
// • 9,12,13,14 nằm trên một cột dọc bên trái (x=32); 10,11 nằm trên một cột dọc (x=18).
// • Cột dọc 12-13-14 (x=32) VUÔNG GÓC với hàng ngang 14-15-16 (y=22), gặp nhau tại 14;
// khoảng cách 13↔14 được giãn rộng.
// • 1-8-22-19-15 cùng cột dọc giữa (x=50); 3-4-5-6-16 cùng cột dọc phải (x=68);
// 7-17 cùng cột dọc (x=82).
// • 21-22-23 cùng một hàng ngang (y=56); 13-20 cùng một hàng ngang (y=40).
// • Nhóm giữa 13-18-19-20 được kéo lên cao, giãn xa khỏi hàng dưới 12-21-22-23;
// 19 cao hơn (y nhỏ hơn) cả 20 và 13.
// • Band 12-21-22-23-6 (y=56) được giãn cách khỏi hàng 9-8-5 (y=72).
const mapPositions: Record<number, { x: number; y: number }> = {
1: { x: 50, y: 90 },
2: { x: 32, y: 90 },
3: { x: 68, y: 90 },
4: { x: 68, y: 81 },
5: { x: 68, y: 72 },
6: { x: 68, y: 56 },
7: { x: 82, y: 76 },
8: { x: 50, y: 72 },
9: { x: 32, y: 72 },
10: { x: 18, y: 72 },
11: { x: 18, y: 60 },
12: { x: 32, y: 56 },
13: { x: 32, y: 40 },
14: { x: 32, y: 22 },
15: { x: 50, y: 22 },
16: { x: 68, y: 22 },
17: { x: 82, y: 34 },
18: { x: 64, y: 42 },
19: { x: 50, y: 32 },
20: { x: 42, y: 40 },
21: { x: 40, y: 56 },
22: { x: 50, y: 56 },
23: { x: 60, y: 56 },
};
const sceneId = (sceneNumber: number) => `scene-${sceneNumber}`;
// Phương vị từ cảnh `from` tới cảnh `to` theo bản đồ → yaw mũi tên trong ảnh 360.
// Quy ước: hướng "lên trên" của bản đồ (giảm y) = yaw 0 (phía trước).
const bearingBetween = (from: number, to: number) => {
const a = mapPositions[from];
const b = mapPositions[to];
const dx = b.x - a.x;
const dy = b.y - a.y;
const deg = (Math.atan2(dx, -dy) * 180) / Math.PI;
return Math.round(deg);
};
const scenes: TourScene[] = seeds.map((seed, index) => {
const hotspots = [];
if (index < seeds.length - 1) {
hotspots.push({
targetId: sceneId(index + 1),
label: seeds[index + 1].title,
yaw: 0,
pitch: -8,
});
}
if (index > 0) {
hotspots.push({
targetId: sceneId(index - 1),
label: seeds[index - 1].title,
yaw: 180,
pitch: -8,
});
}
const sceneNumber = index + 1;
// Sơ đồ tuyến dạng ngoằn ngoèo: cổng ở dưới (y lớn) tiến dần lên trên (y nhỏ).
const progress = index / (seeds.length - 1);
const mapPosition = {
x: index % 2 === 0 ? 32 : 68,
y: Math.round(92 - progress * 84),
};
// Mũi tên hướng đi: mỗi đích đến trong đồ thị = một hotspot, đặt theo phương vị
// bản đồ để hướng mũi tên khớp với mini-map. Tinh chỉnh yaw trực quan khi chạy dev.
const hotspots: Hotspot[] = (adjacency[sceneNumber] ?? []).map((target) => ({
targetId: sceneId(target),
label: seeds[target - 1].title,
yaw: bearingBetween(sceneNumber, target),
pitch: -8,
}));
return {
id: sceneId(index),
order: String(index + 1).padStart(2, "0"),
id: sceneId(sceneNumber),
order: String(sceneNumber).padStart(2, "0"),
title: seed.title,
location: seed.location,
image: imageFor(index),
image: imageForFile(seed.imageFile),
initialYaw: seed.initialYaw ?? 0,
mapPosition,
mapPosition: mapPositions[sceneNumber],
hotspots,
infoMarkers: seed.infoMarkers,
};
});
const mapRoutes = scenes
.slice(0, -1)
.map((scene, index) => ({ from: scene.id, to: scenes[index + 1].id }));
// Đường nối trên mini-map = các cạnh (không hướng) suy ra từ đồ thị hướng đi,
// đã loại trùng (cạnh a-b chỉ vẽ một lần).
const mapRoutes = (() => {
const seen = new Set<string>();
const routes: { from: string; to: string }[] = [];
Object.entries(adjacency).forEach(([fromStr, targets]) => {
const from = Number(fromStr);
targets.forEach((to) => {
const key = from < to ? `${from}-${to}` : `${to}-${from}`;
if (seen.has(key)) {
return;
}
seen.add(key);
routes.push({ from: sceneId(from), to: sceneId(to) });
});
});
return routes;
})();
export const chuaThaiLacTour: Omit<TourConfig, "sceneById" | "locationGroups"> = {
id: "chua-thai-lac",
@@ -152,10 +254,10 @@ export const chuaThaiLacTour: Omit<TourConfig, "sceneById" | "locationGroups"> =
welcomeAccent: "Pháp Vân tự",
welcomeSubtitle:
"Tham quan 360° ngôi chùa cổ thờ Pháp Vân, nổi tiếng với mảng chạm khắc gỗ thời Trần.",
welcomeBackgroundImage: imageFor(7), // 08.jpg — Chính sảnh
welcomeBackgroundImage: imageForFile(8), // 08.jpg — Chính sảnh
welcomeCards: [
{ image: imageFor(0), label: "Cổng chùa", rotate: "-rotate-6", sceneId: "scene-1" },
{ image: imageFor(19), label: "Đài Quan Âm", rotate: "rotate-5", sceneId: "scene-20" },
{ image: imageForFile(1), label: "Cổng chùa", rotate: "-rotate-6", sceneId: "scene-1" },
{ image: imageForFile(10), label: "Đài Quan Âm", rotate: "rotate-5", sceneId: "scene-10" },
],
backgroundAudio: "", // chưa có nhạc nền; engine bỏ qua chuỗi rỗng (không render thẻ audio)
practicalInfo: {