@chocodrop/mcp 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/bin/chocodrop-mcp.js +13 -0
- package/package.json +42 -0
- package/runtime/.chocodrop-mcp-runtime +1 -0
- package/runtime/asset-bridge.js +460 -0
- package/runtime/mcp-server.js +107 -0
- package/runtime/site/examples/basic/README.md +38 -0
- package/runtime/site/examples/basic/index.html +1730 -0
- package/runtime/site/examples/bookmarklet-v2.html +141 -0
- package/runtime/site/examples/bookmarklet.html +126 -0
- package/runtime/site/examples/lofi-room/index.html +2570 -0
- package/runtime/site/examples/lofi-room/sandbox-lite.html +124 -0
- package/runtime/site/examples/music-garden/index.html +2150 -0
- package/runtime/site/examples/pixel-ocean/index.html +1780 -0
- package/runtime/site/examples/react-three-fiber/AdvancedExample.jsx +338 -0
- package/runtime/site/examples/react-three-fiber/App.jsx +97 -0
- package/runtime/site/examples/react-three-fiber/README.md +294 -0
- package/runtime/site/examples/react-three-fiber/package.json +25 -0
- package/runtime/site/examples/sdk-test/README.md +70 -0
- package/runtime/site/examples/space/index.html +1975 -0
- package/runtime/site/examples/toy-city/index.html +2288 -0
- package/runtime/site/examples/wabi-sabi/index.html +2515 -0
- package/runtime/site/getting-started.html +84 -0
- package/runtime/site/index.html +1370 -0
- package/runtime/site/public/CommandUI.js +6341 -0
- package/runtime/site/public/LiveCommandClient.js +432 -0
- package/runtime/site/public/SceneManager.js +5493 -0
- package/runtime/site/public/bookmarklet.js +206 -0
- package/runtime/site/public/bootstrap.js +50 -0
- package/runtime/site/public/chocodrop-demo.umd.js +24020 -0
- package/runtime/site/public/chocodrop-demo.umd.min.js +24020 -0
- package/runtime/site/public/chocodrop.esm.js +2 -0
- package/runtime/site/public/html-sandbox/frame.js +865 -0
- package/runtime/site/public/icons/icon-192.png +0 -0
- package/runtime/site/public/icons/icon-512.png +0 -0
- package/runtime/site/public/icons/icon-maskable-512.png +0 -0
- package/runtime/site/public/immersive-launcher.js +175 -0
- package/runtime/site/public/immersive.html +165 -0
- package/runtime/site/public/index.html +1011 -0
- package/runtime/site/public/index.js +9 -0
- package/runtime/site/public/load-chocodrop.js +25 -0
- package/runtime/site/public/load-three.js +26 -0
- package/runtime/site/public/manifest.webmanifest +59 -0
- package/runtime/site/public/pwa-bootstrap.js +128 -0
- package/runtime/site/public/sample-card.svg +15 -0
- package/runtime/site/public/service-worker.js +117 -0
- package/runtime/site/public/translation-dictionary.js +257 -0
- package/runtime/site/public/xr/xr-ui.css +338 -0
- package/runtime/site/public/xr-viewer.html +263 -0
- package/runtime/site/src/client/local-bridge.js +83 -0
|
@@ -0,0 +1,2570 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ChocoDrop - Lo-fi Room</title>
|
|
7
|
+
<script type="importmap">
|
|
8
|
+
{
|
|
9
|
+
"imports": {
|
|
10
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
11
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
<style>
|
|
16
|
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap');
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
padding: 0;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
font-family: 'Poppins', sans-serif;
|
|
23
|
+
background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#info {
|
|
27
|
+
position: fixed;
|
|
28
|
+
top: 16px;
|
|
29
|
+
left: 16px;
|
|
30
|
+
color: #4a4a6a;
|
|
31
|
+
background: rgba(255, 255, 255, 0.9);
|
|
32
|
+
backdrop-filter: blur(10px);
|
|
33
|
+
-webkit-backdrop-filter: blur(10px);
|
|
34
|
+
border: 2px solid rgba(142, 197, 252, 0.4);
|
|
35
|
+
padding: 14px 18px;
|
|
36
|
+
border-radius: 12px;
|
|
37
|
+
font-size: 12px;
|
|
38
|
+
font-weight: 400;
|
|
39
|
+
z-index: 2000;
|
|
40
|
+
max-width: 280px;
|
|
41
|
+
line-height: 1.6;
|
|
42
|
+
box-shadow: 0 8px 20px rgba(142, 197, 252, 0.3);
|
|
43
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#info.hidden {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
transform: translateY(-10px);
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.close-btn {
|
|
53
|
+
position: absolute;
|
|
54
|
+
top: 8px;
|
|
55
|
+
right: 8px;
|
|
56
|
+
background: none;
|
|
57
|
+
border: none;
|
|
58
|
+
font-size: 16px;
|
|
59
|
+
color: #8ec5fc;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
width: 20px;
|
|
62
|
+
height: 20px;
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
transition: background-color 0.2s ease;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.close-btn:hover {
|
|
71
|
+
background-color: rgba(142, 197, 252, 0.2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* 統合メニュー */
|
|
75
|
+
.unified-menu {
|
|
76
|
+
position: fixed;
|
|
77
|
+
top: 16px;
|
|
78
|
+
right: 20px;
|
|
79
|
+
z-index: 1000;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.menu-toggle {
|
|
83
|
+
background: rgba(255, 255, 255, 0.15);
|
|
84
|
+
backdrop-filter: blur(16px);
|
|
85
|
+
-webkit-backdrop-filter: blur(16px);
|
|
86
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
87
|
+
color: #2d3748;
|
|
88
|
+
padding: 10px 14px;
|
|
89
|
+
border-radius: 8px;
|
|
90
|
+
font-size: 18px;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
93
|
+
box-shadow: 0 4px 12px rgba(142, 197, 252, 0.2), 0 2px 6px rgba(0, 0, 0, 0.05);
|
|
94
|
+
width: 44px;
|
|
95
|
+
height: 44px;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
opacity: 0.8;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.menu-toggle:hover {
|
|
103
|
+
background: rgba(255, 255, 255, 0.25);
|
|
104
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
105
|
+
transform: scale(1.1) translateY(-2px);
|
|
106
|
+
box-shadow: 0 6px 16px rgba(142, 197, 252, 0.3), 0 3px 8px rgba(0, 0, 0, 0.1);
|
|
107
|
+
opacity: 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.menu-dropdown {
|
|
111
|
+
position: absolute;
|
|
112
|
+
top: 56px;
|
|
113
|
+
right: 0;
|
|
114
|
+
min-width: 220px;
|
|
115
|
+
background: rgba(255, 255, 255, 0.85);
|
|
116
|
+
backdrop-filter: blur(16px) saturate(180%);
|
|
117
|
+
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
118
|
+
border: 1px solid rgba(142, 197, 252, 0.2);
|
|
119
|
+
border-radius: 12px;
|
|
120
|
+
box-shadow: 0 8px 24px rgba(142, 197, 252, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
121
|
+
opacity: 0;
|
|
122
|
+
visibility: hidden;
|
|
123
|
+
transform: translateY(-10px);
|
|
124
|
+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.menu-dropdown.active {
|
|
129
|
+
opacity: 1;
|
|
130
|
+
visibility: visible;
|
|
131
|
+
transform: translateY(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.menu-item {
|
|
135
|
+
display: block;
|
|
136
|
+
padding: 12px 16px;
|
|
137
|
+
color: #2d3748;
|
|
138
|
+
text-decoration: none;
|
|
139
|
+
font-size: 13px;
|
|
140
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
141
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
background: none;
|
|
144
|
+
border-left: none;
|
|
145
|
+
border-right: none;
|
|
146
|
+
border-top: none;
|
|
147
|
+
width: 100%;
|
|
148
|
+
text-align: left;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.menu-item:last-child {
|
|
152
|
+
border-bottom: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.menu-item:hover {
|
|
156
|
+
background: rgba(142, 197, 252, 0.08);
|
|
157
|
+
padding-left: 20px;
|
|
158
|
+
border-left: 2px solid rgba(142, 197, 252, 0.4);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.menu-item.has-submenu {
|
|
162
|
+
position: relative;
|
|
163
|
+
padding: 0;
|
|
164
|
+
border: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.submenu-toggle {
|
|
168
|
+
display: block;
|
|
169
|
+
padding: 12px 16px;
|
|
170
|
+
color: #2d3748;
|
|
171
|
+
font-size: 13px;
|
|
172
|
+
background: none;
|
|
173
|
+
border: none;
|
|
174
|
+
width: 100%;
|
|
175
|
+
text-align: left;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
178
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.submenu-toggle:hover {
|
|
182
|
+
background: rgba(142, 197, 252, 0.08);
|
|
183
|
+
padding-left: 20px;
|
|
184
|
+
border-left: 2px solid rgba(142, 197, 252, 0.4);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.submenu {
|
|
188
|
+
display: none;
|
|
189
|
+
background: rgba(142, 197, 252, 0.03);
|
|
190
|
+
border-top: 1px solid rgba(142, 197, 252, 0.1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.submenu.active {
|
|
194
|
+
display: block;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.submenu .menu-item {
|
|
198
|
+
padding-left: 32px;
|
|
199
|
+
font-size: 12px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.submenu .menu-item:hover {
|
|
203
|
+
padding-left: 36px;
|
|
204
|
+
background: rgba(142, 197, 252, 0.12);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#info strong {
|
|
208
|
+
font-size: 15px;
|
|
209
|
+
font-weight: 600;
|
|
210
|
+
color: #8ec5fc;
|
|
211
|
+
display: block;
|
|
212
|
+
margin-bottom: 10px;
|
|
213
|
+
text-align: center;
|
|
214
|
+
letter-spacing: 0.5px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.controls {
|
|
218
|
+
margin-top: 8px;
|
|
219
|
+
padding-top: 8px;
|
|
220
|
+
border-top: 1px solid rgba(142, 197, 252, 0.3);
|
|
221
|
+
font-size: 11px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.control-item {
|
|
225
|
+
margin: 4px 0;
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: 8px;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.key {
|
|
232
|
+
background: rgba(142, 197, 252, 0.2);
|
|
233
|
+
padding: 2px 6px;
|
|
234
|
+
border-radius: 4px;
|
|
235
|
+
font-weight: 500;
|
|
236
|
+
min-width: fit-content;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
canvas {
|
|
240
|
+
display: block;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* 音響ステータス表示 */
|
|
244
|
+
#audio-status {
|
|
245
|
+
position: fixed;
|
|
246
|
+
top: 70px;
|
|
247
|
+
right: 20px;
|
|
248
|
+
z-index: 100;
|
|
249
|
+
background: rgba(255, 255, 255, 0.9);
|
|
250
|
+
backdrop-filter: blur(10px);
|
|
251
|
+
-webkit-backdrop-filter: blur(10px);
|
|
252
|
+
padding: 10px 15px;
|
|
253
|
+
border-radius: 20px;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
border: 2px solid rgba(142, 197, 252, 0.4);
|
|
256
|
+
box-shadow: 0 4px 12px rgba(142, 197, 252, 0.3);
|
|
257
|
+
color: #4a4a6a;
|
|
258
|
+
display: none;
|
|
259
|
+
transition: opacity 0.3s ease;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#audio-status.active {
|
|
263
|
+
display: block;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#audio-status .status-icon {
|
|
267
|
+
display: inline-block;
|
|
268
|
+
margin-right: 5px;
|
|
269
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@keyframes pulse {
|
|
273
|
+
0%, 100% { opacity: 0.8; transform: scale(1); }
|
|
274
|
+
50% { opacity: 1; transform: scale(1.1); }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* メニューアイコン */
|
|
278
|
+
.menu-icon {
|
|
279
|
+
display: inline-block;
|
|
280
|
+
margin-right: 8px;
|
|
281
|
+
animation: menu-bounce 2s ease-in-out infinite;
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
transition: transform 0.2s ease;
|
|
284
|
+
text-shadow: 0 0 10px currentColor;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.menu-icon:hover {
|
|
288
|
+
transform: scale(1.4);
|
|
289
|
+
animation: menu-bounce-fast 0.8s ease-in-out infinite;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@keyframes menu-bounce {
|
|
293
|
+
0%, 100% {
|
|
294
|
+
filter: brightness(1) drop-shadow(0 0 8px currentColor);
|
|
295
|
+
transform: scale(1) translateY(0px);
|
|
296
|
+
}
|
|
297
|
+
25% {
|
|
298
|
+
filter: brightness(1.3) drop-shadow(0 0 15px currentColor);
|
|
299
|
+
transform: scale(1.1) translateY(-4px);
|
|
300
|
+
}
|
|
301
|
+
50% {
|
|
302
|
+
filter: brightness(1.5) drop-shadow(0 0 20px currentColor);
|
|
303
|
+
transform: scale(1.15) translateY(-8px);
|
|
304
|
+
}
|
|
305
|
+
75% {
|
|
306
|
+
filter: brightness(1.3) drop-shadow(0 0 15px currentColor);
|
|
307
|
+
transform: scale(1.1) translateY(-4px);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@keyframes menu-bounce-fast {
|
|
312
|
+
0%, 100% {
|
|
313
|
+
filter: brightness(1.4) drop-shadow(0 0 15px currentColor);
|
|
314
|
+
transform: translateY(0px);
|
|
315
|
+
}
|
|
316
|
+
50% {
|
|
317
|
+
filter: brightness(1.8) drop-shadow(0 0 25px currentColor);
|
|
318
|
+
transform: translateY(-12px);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.subtitle {
|
|
323
|
+
color: #6b7280;
|
|
324
|
+
font-size: 11px;
|
|
325
|
+
margin-top: 6px;
|
|
326
|
+
font-style: italic;
|
|
327
|
+
text-align: center;
|
|
328
|
+
}
|
|
329
|
+
</style>
|
|
330
|
+
</head>
|
|
331
|
+
<body>
|
|
332
|
+
<!-- 音響ステータス表示 -->
|
|
333
|
+
<div id="audio-status">
|
|
334
|
+
<span class="status-icon">🎵</span><span id="audio-text">音声待機中...</span>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<!-- Main Info Panel -->
|
|
338
|
+
<div id="info">
|
|
339
|
+
<button class="close-btn" onclick="hideInfoPanel()">×</button>
|
|
340
|
+
<strong>🎧 Lo-fi Room</strong>
|
|
341
|
+
|
|
342
|
+
<div class="subtitle">
|
|
343
|
+
自分だけの小さな癒しの空間
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
💡 基本操作:<br>
|
|
347
|
+
1. 思い出の写真や動画をインポート<br>
|
|
348
|
+
2. 壁やデスクに配置してみよう<br>
|
|
349
|
+
3. Lo-fi音楽と一緒にリラックス<br>
|
|
350
|
+
4. オブジェクトをクリック → WASDキーで移動<br>
|
|
351
|
+
|
|
352
|
+
<div class="controls">
|
|
353
|
+
<div class="control-item">
|
|
354
|
+
<span class="key">@</span>
|
|
355
|
+
<span>ChocoDrop起動</span>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<!-- 統合メニュー -->
|
|
361
|
+
<div class="unified-menu">
|
|
362
|
+
<button class="menu-toggle" id="unifiedMenuToggle" aria-label="Menu">☰</button>
|
|
363
|
+
<div class="menu-dropdown" id="unifiedMenuDropdown">
|
|
364
|
+
<button class="menu-item" id="showInfoBtn"><span class="menu-icon">📖</span> このワールドについて</button>
|
|
365
|
+
|
|
366
|
+
<div class="menu-item has-submenu">
|
|
367
|
+
<button class="submenu-toggle" id="worldsMenuToggle"><span class="menu-icon">🌍</span> 世界選択 <span style="font-size: 10px;">▼</span></button>
|
|
368
|
+
<div class="submenu" id="worldsSubmenu">
|
|
369
|
+
<a href="../basic/index.html" class="menu-item"><span class="menu-icon">🍫</span> はじまりの国</a>
|
|
370
|
+
<a href="../pixel-ocean/index.html" class="menu-item"><span class="menu-icon">🌊</span> 海底世界</a>
|
|
371
|
+
<a href="../music-garden/index.html" class="menu-item"><span class="menu-icon">🌸</span> 硝子の花</a>
|
|
372
|
+
<a href="../space/index.html" class="menu-item"><span class="menu-icon">🌌</span> 宇宙空間</a>
|
|
373
|
+
<a href="../toy-city/index.html" class="menu-item"><span class="menu-icon">🎡</span> AI遊園地</a>
|
|
374
|
+
<a href="../wabi-sabi/index.html" class="menu-item"><span class="menu-icon">🖤</span> 侘び寂びの世界</a>
|
|
375
|
+
<a href="../classic-cinema/index.html" class="menu-item"><span class="menu-icon">🎬</span> クラシックシネマ</a>
|
|
376
|
+
<a href="index.html" class="menu-item"><span class="menu-icon">🎧</span> Lo-fiルーム(現在地)</a>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<a href="../../index.html" class="menu-item"><span class="menu-icon">🏠</span> Home</a>
|
|
381
|
+
<a href="https://github.com/nyukicorn/chocodrop" class="menu-item" target="_blank"><span class="menu-icon">🔗</span> GitHub</a>
|
|
382
|
+
|
|
383
|
+
<button class="menu-item" id="vrMenuButton"><span class="menu-icon">🥽</span> VR</button>
|
|
384
|
+
<button class="menu-item" id="arMenuButton"><span class="menu-icon">🥽</span> AR</button>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<!-- Three.js + ChocoDrop (module preload) -->
|
|
389
|
+
<script type="module">
|
|
390
|
+
import ensureChocoDrop from '../../public/load-chocodrop.js';
|
|
391
|
+
|
|
392
|
+
// 🎯 情報パネル制御を先行初期化(ローダー待ちの間も操作可能に)
|
|
393
|
+
let autoHideTimer = null;
|
|
394
|
+
|
|
395
|
+
const getInfoPanel = () => document.getElementById('info');
|
|
396
|
+
|
|
397
|
+
const clearInfoPanelAutoHide = () => {
|
|
398
|
+
if (autoHideTimer) {
|
|
399
|
+
clearTimeout(autoHideTimer);
|
|
400
|
+
autoHideTimer = null;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const scheduleInfoPanelAutoHide = () => {
|
|
405
|
+
clearInfoPanelAutoHide();
|
|
406
|
+
autoHideTimer = window.setTimeout(() => {
|
|
407
|
+
if (typeof window.hideInfoPanel === 'function') {
|
|
408
|
+
window.hideInfoPanel();
|
|
409
|
+
}
|
|
410
|
+
}, 30000);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
window.hideInfoPanel = function hideInfoPanel() {
|
|
414
|
+
const infoPanel = getInfoPanel();
|
|
415
|
+
if (!infoPanel) {
|
|
416
|
+
clearInfoPanelAutoHide();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
infoPanel.classList.add('hidden');
|
|
421
|
+
window.setTimeout(() => {
|
|
422
|
+
if (infoPanel.classList.contains('hidden')) {
|
|
423
|
+
infoPanel.style.display = 'none';
|
|
424
|
+
}
|
|
425
|
+
}, 300);
|
|
426
|
+
|
|
427
|
+
clearInfoPanelAutoHide();
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
window.showInfoPanel = function showInfoPanel() {
|
|
431
|
+
const infoPanel = getInfoPanel();
|
|
432
|
+
if (!infoPanel) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
infoPanel.style.display = 'block';
|
|
437
|
+
window.requestAnimationFrame(() => {
|
|
438
|
+
infoPanel.classList.remove('hidden');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
scheduleInfoPanelAutoHide();
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
(async () => {
|
|
445
|
+
await ensureChocoDrop();
|
|
446
|
+
|
|
447
|
+
const THREE = window.THREE;
|
|
448
|
+
const ChocoDrop = window.ChocoDrop;
|
|
449
|
+
|
|
450
|
+
// 統合メニューの制御
|
|
451
|
+
(function() {
|
|
452
|
+
const menuToggle = document.getElementById('unifiedMenuToggle');
|
|
453
|
+
const menuDropdown = document.getElementById('unifiedMenuDropdown');
|
|
454
|
+
const worldsMenuToggle = document.getElementById('worldsMenuToggle');
|
|
455
|
+
const worldsSubmenu = document.getElementById('worldsSubmenu');
|
|
456
|
+
const showInfoBtn = document.getElementById('showInfoBtn');
|
|
457
|
+
const infoPanel = document.getElementById('info');
|
|
458
|
+
|
|
459
|
+
// 初回訪問チェック
|
|
460
|
+
const FIRST_VISIT_KEY = 'lofi_room_visited';
|
|
461
|
+
const hasVisited = localStorage.getItem(FIRST_VISIT_KEY);
|
|
462
|
+
|
|
463
|
+
if (!hasVisited) {
|
|
464
|
+
if (infoPanel) {
|
|
465
|
+
infoPanel.style.display = 'block';
|
|
466
|
+
}
|
|
467
|
+
localStorage.setItem(FIRST_VISIT_KEY, 'true');
|
|
468
|
+
} else {
|
|
469
|
+
if (infoPanel) {
|
|
470
|
+
infoPanel.style.display = 'none';
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (menuToggle && menuDropdown) {
|
|
475
|
+
menuToggle.addEventListener('click', (e) => {
|
|
476
|
+
e.stopPropagation();
|
|
477
|
+
menuDropdown.classList.toggle('active');
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
document.addEventListener('click', (e) => {
|
|
481
|
+
if (!menuToggle.contains(e.target) && !menuDropdown.contains(e.target)) {
|
|
482
|
+
menuDropdown.classList.remove('active');
|
|
483
|
+
if (worldsSubmenu) {
|
|
484
|
+
worldsSubmenu.classList.remove('active');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
document.addEventListener('keydown', (e) => {
|
|
490
|
+
if (e.key === 'Escape') {
|
|
491
|
+
menuDropdown.classList.remove('active');
|
|
492
|
+
if (worldsSubmenu) {
|
|
493
|
+
worldsSubmenu.classList.remove('active');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (worldsMenuToggle && worldsSubmenu) {
|
|
500
|
+
worldsMenuToggle.addEventListener('click', (e) => {
|
|
501
|
+
e.stopPropagation();
|
|
502
|
+
worldsSubmenu.classList.toggle('active');
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (showInfoBtn) {
|
|
507
|
+
showInfoBtn.addEventListener('click', () => {
|
|
508
|
+
if (typeof showInfoPanel === 'function') {
|
|
509
|
+
showInfoPanel();
|
|
510
|
+
} else {
|
|
511
|
+
const panel = document.getElementById('info');
|
|
512
|
+
if (panel) {
|
|
513
|
+
panel.style.display = 'block';
|
|
514
|
+
panel.classList.remove('hidden');
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
menuDropdown.classList.remove('active');
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
window.unifiedMenuVRButton = document.getElementById('vrMenuButton');
|
|
522
|
+
window.unifiedMenuARButton = document.getElementById('arMenuButton');
|
|
523
|
+
})();
|
|
524
|
+
|
|
525
|
+
console.log('THREE loaded:', typeof THREE !== 'undefined');
|
|
526
|
+
|
|
527
|
+
// Basic Three.js setup
|
|
528
|
+
const scene = new THREE.Scene();
|
|
529
|
+
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
530
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
531
|
+
|
|
532
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
533
|
+
renderer.shadowMap.enabled = true;
|
|
534
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
535
|
+
renderer.xr.enabled = true;
|
|
536
|
+
document.body.appendChild(renderer.domElement);
|
|
537
|
+
|
|
538
|
+
// VR/AR設定
|
|
539
|
+
if (typeof VRButton !== 'undefined' && window.unifiedMenuVRButton) {
|
|
540
|
+
function startVRSession() {
|
|
541
|
+
if ('xr' in navigator) {
|
|
542
|
+
navigator.xr.isSessionSupported('immersive-vr').then(supported => {
|
|
543
|
+
if (supported) {
|
|
544
|
+
const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking', 'layers'] };
|
|
545
|
+
navigator.xr.requestSession('immersive-vr', sessionInit).then(session => {
|
|
546
|
+
session.addEventListener('end', () => {
|
|
547
|
+
renderer.xr.setSession(null);
|
|
548
|
+
});
|
|
549
|
+
renderer.xr.setSession(session);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
window.unifiedMenuVRButton.onclick = startVRSession;
|
|
556
|
+
if ('xr' in navigator) {
|
|
557
|
+
navigator.xr.isSessionSupported('immersive-vr').then(supported => {
|
|
558
|
+
if (!supported) window.unifiedMenuVRButton.style.display = 'none';
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
window.unifiedMenuVRButton.style.display = 'none';
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (typeof ARButton !== 'undefined' && window.unifiedMenuARButton) {
|
|
566
|
+
function startARSession() {
|
|
567
|
+
if ('xr' in navigator) {
|
|
568
|
+
navigator.xr.isSessionSupported('immersive-ar').then(supported => {
|
|
569
|
+
if (supported) {
|
|
570
|
+
const sessionInit = {
|
|
571
|
+
requiredFeatures: ['hit-test'],
|
|
572
|
+
optionalFeatures: ['dom-overlay'],
|
|
573
|
+
domOverlay: { root: document.body }
|
|
574
|
+
};
|
|
575
|
+
navigator.xr.requestSession('immersive-ar', sessionInit).then(session => {
|
|
576
|
+
session.addEventListener('end', () => {
|
|
577
|
+
renderer.xr.setSession(null);
|
|
578
|
+
});
|
|
579
|
+
renderer.xr.setSession(session);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
window.unifiedMenuARButton.onclick = startARSession;
|
|
586
|
+
if ('xr' in navigator) {
|
|
587
|
+
navigator.xr.isSessionSupported('immersive-ar').then(supported => {
|
|
588
|
+
if (!supported) window.unifiedMenuARButton.style.display = 'none';
|
|
589
|
+
});
|
|
590
|
+
} else {
|
|
591
|
+
window.unifiedMenuARButton.style.display = 'none';
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Lo-fi Room の世界
|
|
596
|
+
scene.background = new THREE.Color(0xe8d5f2);
|
|
597
|
+
scene.fog = new THREE.Fog(0xe8d5f2, 10, 50);
|
|
598
|
+
|
|
599
|
+
// 柔らかい環境光
|
|
600
|
+
const ambientLight = new THREE.AmbientLight(0xfff5f0, 0.8);
|
|
601
|
+
scene.add(ambientLight);
|
|
602
|
+
|
|
603
|
+
// メインライト(窓からの光)
|
|
604
|
+
const directionalLight1 = new THREE.DirectionalLight(0xfff5e6, 0.7);
|
|
605
|
+
directionalLight1.position.set(10, 15, 8);
|
|
606
|
+
directionalLight1.castShadow = true;
|
|
607
|
+
directionalLight1.shadow.mapSize.width = 2048;
|
|
608
|
+
directionalLight1.shadow.mapSize.height = 2048;
|
|
609
|
+
scene.add(directionalLight1);
|
|
610
|
+
|
|
611
|
+
// フィルライト
|
|
612
|
+
const directionalLight2 = new THREE.DirectionalLight(0xe0c3fc, 0.4);
|
|
613
|
+
directionalLight2.position.set(-8, 10, -5);
|
|
614
|
+
scene.add(directionalLight2);
|
|
615
|
+
|
|
616
|
+
// 床
|
|
617
|
+
const floorGeometry = new THREE.PlaneGeometry(20, 20);
|
|
618
|
+
const floorMaterial = new THREE.MeshStandardMaterial({
|
|
619
|
+
color: 0xf5e6ff,
|
|
620
|
+
metalness: 0.1,
|
|
621
|
+
roughness: 0.9
|
|
622
|
+
});
|
|
623
|
+
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
|
624
|
+
floor.rotation.x = -Math.PI / 2;
|
|
625
|
+
floor.position.y = 0;
|
|
626
|
+
floor.receiveShadow = true;
|
|
627
|
+
scene.add(floor);
|
|
628
|
+
|
|
629
|
+
// 壁(3面)
|
|
630
|
+
function createWall(width, height, x, y, z, rotationY) {
|
|
631
|
+
const wallGeometry = new THREE.PlaneGeometry(width, height);
|
|
632
|
+
const wallMaterial = new THREE.MeshStandardMaterial({
|
|
633
|
+
color: 0xffd4e5,
|
|
634
|
+
metalness: 0.1,
|
|
635
|
+
roughness: 0.8
|
|
636
|
+
});
|
|
637
|
+
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
|
|
638
|
+
wall.position.set(x, y, z);
|
|
639
|
+
wall.rotation.y = rotationY;
|
|
640
|
+
wall.receiveShadow = true;
|
|
641
|
+
scene.add(wall);
|
|
642
|
+
return wall;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// 背面の壁
|
|
646
|
+
const backWall = createWall(12, 8, 0, 4, -6, 0);
|
|
647
|
+
// 左の壁
|
|
648
|
+
const leftWall = createWall(12, 8, -6, 4, 0, Math.PI / 2);
|
|
649
|
+
// 右の壁
|
|
650
|
+
const rightWall = createWall(12, 8, 6, 4, 0, -Math.PI / 2);
|
|
651
|
+
|
|
652
|
+
// ラグ
|
|
653
|
+
const rugGeometry = new THREE.PlaneGeometry(5, 4);
|
|
654
|
+
const rugMaterial = new THREE.MeshStandardMaterial({
|
|
655
|
+
color: 0xb4d5fe,
|
|
656
|
+
metalness: 0.1,
|
|
657
|
+
roughness: 1
|
|
658
|
+
});
|
|
659
|
+
const rug = new THREE.Mesh(rugGeometry, rugMaterial);
|
|
660
|
+
rug.rotation.x = -Math.PI / 2;
|
|
661
|
+
rug.position.set(0, 0.01, 0.5);
|
|
662
|
+
rug.receiveShadow = true;
|
|
663
|
+
scene.add(rug);
|
|
664
|
+
|
|
665
|
+
// ベッド
|
|
666
|
+
function createBed() {
|
|
667
|
+
const bedGroup = new THREE.Group();
|
|
668
|
+
|
|
669
|
+
// ベッドフレーム
|
|
670
|
+
const frameGeometry = new THREE.BoxGeometry(3.5, 0.3, 2);
|
|
671
|
+
const frameMaterial = new THREE.MeshStandardMaterial({
|
|
672
|
+
color: 0xffd4a3,
|
|
673
|
+
metalness: 0.2,
|
|
674
|
+
roughness: 0.7
|
|
675
|
+
});
|
|
676
|
+
const frame = new THREE.Mesh(frameGeometry, frameMaterial);
|
|
677
|
+
frame.position.y = 0.15;
|
|
678
|
+
frame.castShadow = true;
|
|
679
|
+
bedGroup.add(frame);
|
|
680
|
+
|
|
681
|
+
// マットレス
|
|
682
|
+
const mattressGeometry = new THREE.BoxGeometry(3.3, 0.4, 1.8);
|
|
683
|
+
const mattressMaterial = new THREE.MeshStandardMaterial({
|
|
684
|
+
color: 0xffeef8,
|
|
685
|
+
metalness: 0.1,
|
|
686
|
+
roughness: 0.9
|
|
687
|
+
});
|
|
688
|
+
const mattress = new THREE.Mesh(mattressGeometry, mattressMaterial);
|
|
689
|
+
mattress.position.y = 0.5;
|
|
690
|
+
mattress.castShadow = true;
|
|
691
|
+
bedGroup.add(mattress);
|
|
692
|
+
|
|
693
|
+
// ブランケット
|
|
694
|
+
const blanketGeometry = new THREE.BoxGeometry(3, 0.2, 1.5);
|
|
695
|
+
const blanketMaterial = new THREE.MeshStandardMaterial({
|
|
696
|
+
color: 0xb4d5fe,
|
|
697
|
+
metalness: 0.1,
|
|
698
|
+
roughness: 0.8
|
|
699
|
+
});
|
|
700
|
+
const blanket = new THREE.Mesh(blanketGeometry, blanketMaterial);
|
|
701
|
+
blanket.position.set(0, 0.8, -0.2);
|
|
702
|
+
blanket.castShadow = true;
|
|
703
|
+
bedGroup.add(blanket);
|
|
704
|
+
|
|
705
|
+
// 枕
|
|
706
|
+
const pillowGeometry = new THREE.BoxGeometry(0.8, 0.3, 0.6);
|
|
707
|
+
const pillowMaterial = new THREE.MeshStandardMaterial({
|
|
708
|
+
color: 0xfff5f0,
|
|
709
|
+
metalness: 0.1,
|
|
710
|
+
roughness: 0.9
|
|
711
|
+
});
|
|
712
|
+
const pillow = new THREE.Mesh(pillowGeometry, pillowMaterial);
|
|
713
|
+
pillow.position.set(0, 0.85, 0.7);
|
|
714
|
+
pillow.castShadow = true;
|
|
715
|
+
bedGroup.add(pillow);
|
|
716
|
+
|
|
717
|
+
bedGroup.position.set(-3, 0, -3);
|
|
718
|
+
scene.add(bedGroup);
|
|
719
|
+
return bedGroup;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const bed = createBed();
|
|
723
|
+
|
|
724
|
+
// デスク
|
|
725
|
+
function createDesk() {
|
|
726
|
+
const deskGroup = new THREE.Group();
|
|
727
|
+
|
|
728
|
+
// 天板
|
|
729
|
+
const topGeometry = new THREE.BoxGeometry(3, 0.1, 1.5);
|
|
730
|
+
const topMaterial = new THREE.MeshStandardMaterial({
|
|
731
|
+
color: 0xffd4a3,
|
|
732
|
+
metalness: 0.2,
|
|
733
|
+
roughness: 0.6
|
|
734
|
+
});
|
|
735
|
+
const top = new THREE.Mesh(topGeometry, topMaterial);
|
|
736
|
+
top.position.y = 1.2;
|
|
737
|
+
top.castShadow = true;
|
|
738
|
+
deskGroup.add(top);
|
|
739
|
+
|
|
740
|
+
// 脚(4本)
|
|
741
|
+
const legGeometry = new THREE.BoxGeometry(0.1, 1.2, 0.1);
|
|
742
|
+
const legMaterial = new THREE.MeshStandardMaterial({
|
|
743
|
+
color: 0xe8c896,
|
|
744
|
+
metalness: 0.2,
|
|
745
|
+
roughness: 0.7
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
const legPositions = [
|
|
749
|
+
[-1.4, 0.6, -0.7],
|
|
750
|
+
[1.4, 0.6, -0.7],
|
|
751
|
+
[-1.4, 0.6, 0.7],
|
|
752
|
+
[1.4, 0.6, 0.7]
|
|
753
|
+
];
|
|
754
|
+
|
|
755
|
+
legPositions.forEach(pos => {
|
|
756
|
+
const leg = new THREE.Mesh(legGeometry, legMaterial);
|
|
757
|
+
leg.position.set(pos[0], pos[1], pos[2]);
|
|
758
|
+
leg.castShadow = true;
|
|
759
|
+
deskGroup.add(leg);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
deskGroup.position.set(2, 0, 2);
|
|
763
|
+
scene.add(deskGroup);
|
|
764
|
+
return deskGroup;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const desk = createDesk();
|
|
768
|
+
|
|
769
|
+
// ノートPC
|
|
770
|
+
function createLaptop() {
|
|
771
|
+
const laptopGroup = new THREE.Group();
|
|
772
|
+
|
|
773
|
+
// 画面
|
|
774
|
+
const screenGeometry = new THREE.BoxGeometry(1.2, 0.02, 0.8);
|
|
775
|
+
const screenMaterial = new THREE.MeshStandardMaterial({
|
|
776
|
+
color: 0x2d3748,
|
|
777
|
+
metalness: 0.6,
|
|
778
|
+
roughness: 0.3,
|
|
779
|
+
emissive: 0x6366f1,
|
|
780
|
+
emissiveIntensity: 0.3
|
|
781
|
+
});
|
|
782
|
+
const screen = new THREE.Mesh(screenGeometry, screenMaterial);
|
|
783
|
+
screen.position.set(0, 0.4, 0);
|
|
784
|
+
screen.rotation.x = -Math.PI / 6;
|
|
785
|
+
screen.castShadow = true;
|
|
786
|
+
laptopGroup.add(screen);
|
|
787
|
+
|
|
788
|
+
// キーボード
|
|
789
|
+
const keyboardGeometry = new THREE.BoxGeometry(1.2, 0.05, 0.8);
|
|
790
|
+
const keyboardMaterial = new THREE.MeshStandardMaterial({
|
|
791
|
+
color: 0x4a5568,
|
|
792
|
+
metalness: 0.5,
|
|
793
|
+
roughness: 0.4
|
|
794
|
+
});
|
|
795
|
+
const keyboard = new THREE.Mesh(keyboardGeometry, keyboardMaterial);
|
|
796
|
+
keyboard.position.y = 0.025;
|
|
797
|
+
keyboard.castShadow = true;
|
|
798
|
+
laptopGroup.add(keyboard);
|
|
799
|
+
|
|
800
|
+
laptopGroup.position.set(2, 1.25, 2.3);
|
|
801
|
+
scene.add(laptopGroup);
|
|
802
|
+
return laptopGroup;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const laptop = createLaptop();
|
|
806
|
+
|
|
807
|
+
// 椅子
|
|
808
|
+
function createChair() {
|
|
809
|
+
const chairGroup = new THREE.Group();
|
|
810
|
+
|
|
811
|
+
// 座面
|
|
812
|
+
const seatGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.1, 32);
|
|
813
|
+
const seatMaterial = new THREE.MeshStandardMaterial({
|
|
814
|
+
color: 0xffc4d6,
|
|
815
|
+
metalness: 0.2,
|
|
816
|
+
roughness: 0.7
|
|
817
|
+
});
|
|
818
|
+
const seat = new THREE.Mesh(seatGeometry, seatMaterial);
|
|
819
|
+
seat.position.y = 0.8;
|
|
820
|
+
seat.castShadow = true;
|
|
821
|
+
chairGroup.add(seat);
|
|
822
|
+
|
|
823
|
+
// 背もたれ
|
|
824
|
+
const backrestGeometry = new THREE.BoxGeometry(0.8, 0.8, 0.1);
|
|
825
|
+
const backrestMaterial = new THREE.MeshStandardMaterial({
|
|
826
|
+
color: 0xffc4d6,
|
|
827
|
+
metalness: 0.2,
|
|
828
|
+
roughness: 0.7
|
|
829
|
+
});
|
|
830
|
+
const backrest = new THREE.Mesh(backrestGeometry, backrestMaterial);
|
|
831
|
+
backrest.position.set(0, 1.2, -0.4);
|
|
832
|
+
backrest.castShadow = true;
|
|
833
|
+
chairGroup.add(backrest);
|
|
834
|
+
|
|
835
|
+
// ポール
|
|
836
|
+
const poleGeometry = new THREE.CylinderGeometry(0.08, 0.08, 0.8, 16);
|
|
837
|
+
const poleMaterial = new THREE.MeshStandardMaterial({
|
|
838
|
+
color: 0xe8c896,
|
|
839
|
+
metalness: 0.5,
|
|
840
|
+
roughness: 0.4
|
|
841
|
+
});
|
|
842
|
+
const pole = new THREE.Mesh(poleGeometry, poleMaterial);
|
|
843
|
+
pole.position.y = 0.4;
|
|
844
|
+
pole.castShadow = true;
|
|
845
|
+
chairGroup.add(pole);
|
|
846
|
+
|
|
847
|
+
// ベース
|
|
848
|
+
const baseGeometry = new THREE.CylinderGeometry(0.6, 0.6, 0.05, 32);
|
|
849
|
+
const baseMaterial = new THREE.MeshStandardMaterial({
|
|
850
|
+
color: 0xe8c896,
|
|
851
|
+
metalness: 0.5,
|
|
852
|
+
roughness: 0.4
|
|
853
|
+
});
|
|
854
|
+
const base = new THREE.Mesh(baseGeometry, baseMaterial);
|
|
855
|
+
base.position.y = 0.025;
|
|
856
|
+
base.castShadow = true;
|
|
857
|
+
chairGroup.add(base);
|
|
858
|
+
|
|
859
|
+
chairGroup.position.set(2, 0, 0.5);
|
|
860
|
+
scene.add(chairGroup);
|
|
861
|
+
return chairGroup;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const chair = createChair();
|
|
865
|
+
|
|
866
|
+
// 本棚
|
|
867
|
+
function createBookshelf() {
|
|
868
|
+
const shelfGroup = new THREE.Group();
|
|
869
|
+
|
|
870
|
+
// 背板
|
|
871
|
+
const backGeometry = new THREE.BoxGeometry(2, 3, 0.1);
|
|
872
|
+
const backMaterial = new THREE.MeshStandardMaterial({
|
|
873
|
+
color: 0xffd4a3,
|
|
874
|
+
metalness: 0.2,
|
|
875
|
+
roughness: 0.7
|
|
876
|
+
});
|
|
877
|
+
const back = new THREE.Mesh(backGeometry, backMaterial);
|
|
878
|
+
back.castShadow = true;
|
|
879
|
+
shelfGroup.add(back);
|
|
880
|
+
|
|
881
|
+
// 棚板(3段)
|
|
882
|
+
const shelfGeometry = new THREE.BoxGeometry(2, 0.1, 0.4);
|
|
883
|
+
const shelfMaterial = new THREE.MeshStandardMaterial({
|
|
884
|
+
color: 0xe8c896,
|
|
885
|
+
metalness: 0.2,
|
|
886
|
+
roughness: 0.6
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
for (let i = 0; i < 3; i++) {
|
|
890
|
+
const shelf = new THREE.Mesh(shelfGeometry, shelfMaterial);
|
|
891
|
+
shelf.position.set(0, -1 + i * 1, 0.15);
|
|
892
|
+
shelf.castShadow = true;
|
|
893
|
+
shelfGroup.add(shelf);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// カラフルな本を追加(各棚に)
|
|
897
|
+
const bookColors = [
|
|
898
|
+
0xff99cc, 0x99ccff, 0xccff99, 0xffcc99, 0xff99ff,
|
|
899
|
+
0x99ffcc, 0xffccff, 0xccccff, 0xffffcc, 0xffcccc
|
|
900
|
+
];
|
|
901
|
+
|
|
902
|
+
for (let shelfIndex = 0; shelfIndex < 3; shelfIndex++) {
|
|
903
|
+
const numBooks = 8 + Math.floor(Math.random() * 4);
|
|
904
|
+
for (let bookIndex = 0; bookIndex < numBooks; bookIndex++) {
|
|
905
|
+
const bookWidth = 0.08 + Math.random() * 0.06;
|
|
906
|
+
const bookHeight = 0.3 + Math.random() * 0.15;
|
|
907
|
+
const bookDepth = 0.25;
|
|
908
|
+
|
|
909
|
+
const bookGeometry = new THREE.BoxGeometry(bookWidth, bookHeight, bookDepth);
|
|
910
|
+
const bookColor = bookColors[Math.floor(Math.random() * bookColors.length)];
|
|
911
|
+
const bookMaterial = new THREE.MeshStandardMaterial({
|
|
912
|
+
color: bookColor,
|
|
913
|
+
metalness: 0.1,
|
|
914
|
+
roughness: 0.8
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
const book = new THREE.Mesh(bookGeometry, bookMaterial);
|
|
918
|
+
const xOffset = -0.9 + (bookIndex / numBooks) * 1.8;
|
|
919
|
+
book.position.set(
|
|
920
|
+
xOffset,
|
|
921
|
+
-1 + shelfIndex * 1 + 0.05 + bookHeight / 2,
|
|
922
|
+
0.2
|
|
923
|
+
);
|
|
924
|
+
book.castShadow = true;
|
|
925
|
+
|
|
926
|
+
// ランダムに少し傾ける
|
|
927
|
+
book.rotation.z = (Math.random() - 0.5) * 0.1;
|
|
928
|
+
shelfGroup.add(book);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
shelfGroup.position.set(-5, 1.5, -5);
|
|
933
|
+
scene.add(shelfGroup);
|
|
934
|
+
return shelfGroup;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const bookshelf = createBookshelf();
|
|
938
|
+
|
|
939
|
+
// 観葉植物(サボテン)
|
|
940
|
+
function createCactus(x, y, z) {
|
|
941
|
+
const cactusGroup = new THREE.Group();
|
|
942
|
+
|
|
943
|
+
// 鉢
|
|
944
|
+
const potGeometry = new THREE.CylinderGeometry(0.25, 0.2, 0.3, 16);
|
|
945
|
+
const potMaterial = new THREE.MeshStandardMaterial({
|
|
946
|
+
color: 0xd4a574,
|
|
947
|
+
metalness: 0.2,
|
|
948
|
+
roughness: 0.8
|
|
949
|
+
});
|
|
950
|
+
const pot = new THREE.Mesh(potGeometry, potMaterial);
|
|
951
|
+
pot.position.y = 0.15;
|
|
952
|
+
pot.castShadow = true;
|
|
953
|
+
cactusGroup.add(pot);
|
|
954
|
+
|
|
955
|
+
// サボテン本体
|
|
956
|
+
const cactusGeometry = new THREE.CylinderGeometry(0.15, 0.2, 0.8, 8);
|
|
957
|
+
const cactusMaterial = new THREE.MeshStandardMaterial({
|
|
958
|
+
color: 0x7cb342,
|
|
959
|
+
metalness: 0.1,
|
|
960
|
+
roughness: 0.7
|
|
961
|
+
});
|
|
962
|
+
const cactus = new THREE.Mesh(cactusGeometry, cactusMaterial);
|
|
963
|
+
cactus.position.y = 0.7;
|
|
964
|
+
cactus.castShadow = true;
|
|
965
|
+
cactusGroup.add(cactus);
|
|
966
|
+
|
|
967
|
+
cactusGroup.position.set(x, y, z);
|
|
968
|
+
scene.add(cactusGroup);
|
|
969
|
+
return cactusGroup;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const cactus1 = createCactus(4.5, 0, 2);
|
|
973
|
+
const cactus2 = createCactus(-5.5, 3, -5);
|
|
974
|
+
|
|
975
|
+
// 吊り下げ植物
|
|
976
|
+
function createHangingPlant(x, y, z) {
|
|
977
|
+
const plantGroup = new THREE.Group();
|
|
978
|
+
|
|
979
|
+
// 鉢
|
|
980
|
+
const potGeometry = new THREE.SphereGeometry(0.25, 16, 16);
|
|
981
|
+
const potMaterial = new THREE.MeshStandardMaterial({
|
|
982
|
+
color: 0xd4a574,
|
|
983
|
+
metalness: 0.2,
|
|
984
|
+
roughness: 0.8
|
|
985
|
+
});
|
|
986
|
+
const pot = new THREE.Mesh(potGeometry, potMaterial);
|
|
987
|
+
pot.castShadow = true;
|
|
988
|
+
plantGroup.add(pot);
|
|
989
|
+
|
|
990
|
+
// 吊り紐
|
|
991
|
+
const ropeGeometry = new THREE.CylinderGeometry(0.02, 0.02, 1.5, 8);
|
|
992
|
+
const ropeMaterial = new THREE.MeshStandardMaterial({
|
|
993
|
+
color: 0xa0826d,
|
|
994
|
+
metalness: 0.1,
|
|
995
|
+
roughness: 0.9
|
|
996
|
+
});
|
|
997
|
+
const rope = new THREE.Mesh(ropeGeometry, ropeMaterial);
|
|
998
|
+
rope.position.y = 0.75;
|
|
999
|
+
plantGroup.add(rope);
|
|
1000
|
+
|
|
1001
|
+
// 葉っぱ(複数)
|
|
1002
|
+
for (let i = 0; i < 8; i++) {
|
|
1003
|
+
const leafGeometry = new THREE.SphereGeometry(0.15, 8, 8);
|
|
1004
|
+
const leafMaterial = new THREE.MeshStandardMaterial({
|
|
1005
|
+
color: 0x7cb342,
|
|
1006
|
+
metalness: 0.1,
|
|
1007
|
+
roughness: 0.7
|
|
1008
|
+
});
|
|
1009
|
+
const leaf = new THREE.Mesh(leafGeometry, leafMaterial);
|
|
1010
|
+
const angle = (i / 8) * Math.PI * 2;
|
|
1011
|
+
leaf.position.set(
|
|
1012
|
+
Math.cos(angle) * 0.3,
|
|
1013
|
+
-0.3 - Math.random() * 0.5,
|
|
1014
|
+
Math.sin(angle) * 0.3
|
|
1015
|
+
);
|
|
1016
|
+
leaf.castShadow = true;
|
|
1017
|
+
plantGroup.add(leaf);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
plantGroup.position.set(x, y, z);
|
|
1021
|
+
plantGroup.userData.swingPhase = Math.random() * Math.PI * 2;
|
|
1022
|
+
scene.add(plantGroup);
|
|
1023
|
+
return plantGroup;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const hangingPlant1 = createHangingPlant(-4, 6.5, -3);
|
|
1027
|
+
const hangingPlant2 = createHangingPlant(3.5, 6.5, -2);
|
|
1028
|
+
|
|
1029
|
+
// 小さな花(床置き)
|
|
1030
|
+
function createFlowerPot(x, y, z, flowerColor) {
|
|
1031
|
+
const flowerGroup = new THREE.Group();
|
|
1032
|
+
|
|
1033
|
+
// 鉢
|
|
1034
|
+
const potGeometry = new THREE.CylinderGeometry(0.2, 0.15, 0.25, 16);
|
|
1035
|
+
const potMaterial = new THREE.MeshStandardMaterial({
|
|
1036
|
+
color: 0xffd4e5,
|
|
1037
|
+
metalness: 0.2,
|
|
1038
|
+
roughness: 0.7
|
|
1039
|
+
});
|
|
1040
|
+
const pot = new THREE.Mesh(potGeometry, potMaterial);
|
|
1041
|
+
pot.position.y = 0.125;
|
|
1042
|
+
pot.castShadow = true;
|
|
1043
|
+
flowerGroup.add(pot);
|
|
1044
|
+
|
|
1045
|
+
// 茎
|
|
1046
|
+
const stemGeometry = new THREE.CylinderGeometry(0.02, 0.02, 0.4, 8);
|
|
1047
|
+
const stemMaterial = new THREE.MeshStandardMaterial({
|
|
1048
|
+
color: 0x7cb342,
|
|
1049
|
+
metalness: 0.1,
|
|
1050
|
+
roughness: 0.8
|
|
1051
|
+
});
|
|
1052
|
+
const stem = new THREE.Mesh(stemGeometry, stemMaterial);
|
|
1053
|
+
stem.position.y = 0.45;
|
|
1054
|
+
stem.castShadow = true;
|
|
1055
|
+
flowerGroup.add(stem);
|
|
1056
|
+
|
|
1057
|
+
// 花びら(5枚)
|
|
1058
|
+
for (let i = 0; i < 5; i++) {
|
|
1059
|
+
const petalGeometry = new THREE.SphereGeometry(0.1, 8, 8);
|
|
1060
|
+
const petalMaterial = new THREE.MeshStandardMaterial({
|
|
1061
|
+
color: flowerColor,
|
|
1062
|
+
metalness: 0.1,
|
|
1063
|
+
roughness: 0.6,
|
|
1064
|
+
emissive: flowerColor,
|
|
1065
|
+
emissiveIntensity: 0.3
|
|
1066
|
+
});
|
|
1067
|
+
const petal = new THREE.Mesh(petalGeometry, petalMaterial);
|
|
1068
|
+
const angle = (i / 5) * Math.PI * 2;
|
|
1069
|
+
petal.position.set(
|
|
1070
|
+
Math.cos(angle) * 0.12,
|
|
1071
|
+
0.65,
|
|
1072
|
+
Math.sin(angle) * 0.12
|
|
1073
|
+
);
|
|
1074
|
+
petal.castShadow = true;
|
|
1075
|
+
flowerGroup.add(petal);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// 中心
|
|
1079
|
+
const centerGeometry = new THREE.SphereGeometry(0.06, 8, 8);
|
|
1080
|
+
const centerMaterial = new THREE.MeshStandardMaterial({
|
|
1081
|
+
color: 0xffeb3b,
|
|
1082
|
+
metalness: 0.1,
|
|
1083
|
+
roughness: 0.5,
|
|
1084
|
+
emissive: 0xffeb3b,
|
|
1085
|
+
emissiveIntensity: 0.5
|
|
1086
|
+
});
|
|
1087
|
+
const center = new THREE.Mesh(centerGeometry, centerMaterial);
|
|
1088
|
+
center.position.y = 0.65;
|
|
1089
|
+
center.castShadow = true;
|
|
1090
|
+
flowerGroup.add(center);
|
|
1091
|
+
|
|
1092
|
+
flowerGroup.position.set(x, y, z);
|
|
1093
|
+
scene.add(flowerGroup);
|
|
1094
|
+
return flowerGroup;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const flower1 = createFlowerPot(-4.5, 0, -1, 0xff99cc);
|
|
1098
|
+
const flower2 = createFlowerPot(5, 0, -4, 0x99ccff);
|
|
1099
|
+
const flower3 = createFlowerPot(0.5, 1.3, 1.2, 0xffcc99); // デスクの上
|
|
1100
|
+
|
|
1101
|
+
// アコースティックギター
|
|
1102
|
+
function createGuitar() {
|
|
1103
|
+
const guitarGroup = new THREE.Group();
|
|
1104
|
+
|
|
1105
|
+
// ボディ
|
|
1106
|
+
const bodyGeometry = new THREE.SphereGeometry(0.4, 16, 16);
|
|
1107
|
+
const bodyMaterial = new THREE.MeshStandardMaterial({
|
|
1108
|
+
color: 0xd4a574,
|
|
1109
|
+
metalness: 0.3,
|
|
1110
|
+
roughness: 0.6
|
|
1111
|
+
});
|
|
1112
|
+
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
|
1113
|
+
body.scale.set(1, 1, 0.3);
|
|
1114
|
+
body.castShadow = true;
|
|
1115
|
+
guitarGroup.add(body);
|
|
1116
|
+
|
|
1117
|
+
// ネック
|
|
1118
|
+
const neckGeometry = new THREE.CylinderGeometry(0.05, 0.05, 1, 16);
|
|
1119
|
+
const neckMaterial = new THREE.MeshStandardMaterial({
|
|
1120
|
+
color: 0xa0826d,
|
|
1121
|
+
metalness: 0.3,
|
|
1122
|
+
roughness: 0.5
|
|
1123
|
+
});
|
|
1124
|
+
const neck = new THREE.Mesh(neckGeometry, neckMaterial);
|
|
1125
|
+
neck.position.y = 0.7;
|
|
1126
|
+
neck.castShadow = true;
|
|
1127
|
+
guitarGroup.add(neck);
|
|
1128
|
+
|
|
1129
|
+
// ヘッド
|
|
1130
|
+
const headGeometry = new THREE.BoxGeometry(0.15, 0.25, 0.05);
|
|
1131
|
+
const headMaterial = new THREE.MeshStandardMaterial({
|
|
1132
|
+
color: 0xa0826d,
|
|
1133
|
+
metalness: 0.3,
|
|
1134
|
+
roughness: 0.5
|
|
1135
|
+
});
|
|
1136
|
+
const head = new THREE.Mesh(headGeometry, headMaterial);
|
|
1137
|
+
head.position.y = 1.3;
|
|
1138
|
+
head.castShadow = true;
|
|
1139
|
+
guitarGroup.add(head);
|
|
1140
|
+
|
|
1141
|
+
// 弦(6本)
|
|
1142
|
+
for (let i = 0; i < 6; i++) {
|
|
1143
|
+
const stringGeometry = new THREE.CylinderGeometry(0.005, 0.005, 1.3, 8);
|
|
1144
|
+
const stringMaterial = new THREE.MeshStandardMaterial({
|
|
1145
|
+
color: 0xcccccc,
|
|
1146
|
+
metalness: 0.8,
|
|
1147
|
+
roughness: 0.2
|
|
1148
|
+
});
|
|
1149
|
+
const string = new THREE.Mesh(stringGeometry, stringMaterial);
|
|
1150
|
+
string.position.set(-0.075 + i * 0.03, 0.6, 0.1);
|
|
1151
|
+
guitarGroup.add(string);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
guitarGroup.position.set(-2.5, 0.8, -5.8);
|
|
1155
|
+
guitarGroup.rotation.set(0.3, 0, -0.3);
|
|
1156
|
+
guitarGroup.scale.set(0.8, 0.8, 0.8);
|
|
1157
|
+
scene.add(guitarGroup);
|
|
1158
|
+
return guitarGroup;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const guitar = createGuitar();
|
|
1162
|
+
|
|
1163
|
+
// カメラ
|
|
1164
|
+
function createCamera() {
|
|
1165
|
+
const cameraGroup = new THREE.Group();
|
|
1166
|
+
|
|
1167
|
+
// ボディ
|
|
1168
|
+
const bodyGeometry = new THREE.BoxGeometry(0.3, 0.2, 0.25);
|
|
1169
|
+
const bodyMaterial = new THREE.MeshStandardMaterial({
|
|
1170
|
+
color: 0x4a4a6a,
|
|
1171
|
+
metalness: 0.5,
|
|
1172
|
+
roughness: 0.4
|
|
1173
|
+
});
|
|
1174
|
+
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
|
1175
|
+
body.castShadow = true;
|
|
1176
|
+
cameraGroup.add(body);
|
|
1177
|
+
|
|
1178
|
+
// レンズ
|
|
1179
|
+
const lensGeometry = new THREE.CylinderGeometry(0.12, 0.12, 0.15, 16);
|
|
1180
|
+
const lensMaterial = new THREE.MeshStandardMaterial({
|
|
1181
|
+
color: 0x2d3748,
|
|
1182
|
+
metalness: 0.7,
|
|
1183
|
+
roughness: 0.3
|
|
1184
|
+
});
|
|
1185
|
+
const lens = new THREE.Mesh(lensGeometry, lensMaterial);
|
|
1186
|
+
lens.rotation.x = Math.PI / 2;
|
|
1187
|
+
lens.position.z = 0.2;
|
|
1188
|
+
lens.castShadow = true;
|
|
1189
|
+
cameraGroup.add(lens);
|
|
1190
|
+
|
|
1191
|
+
// レンズガラス
|
|
1192
|
+
const glassGeometry = new THREE.CircleGeometry(0.1, 16);
|
|
1193
|
+
const glassMaterial = new THREE.MeshStandardMaterial({
|
|
1194
|
+
color: 0x6366f1,
|
|
1195
|
+
metalness: 0.9,
|
|
1196
|
+
roughness: 0.1,
|
|
1197
|
+
emissive: 0x6366f1,
|
|
1198
|
+
emissiveIntensity: 0.3
|
|
1199
|
+
});
|
|
1200
|
+
const glass = new THREE.Mesh(glassGeometry, glassMaterial);
|
|
1201
|
+
glass.position.z = 0.275;
|
|
1202
|
+
cameraGroup.add(glass);
|
|
1203
|
+
|
|
1204
|
+
// ビューファインダー
|
|
1205
|
+
const finderGeometry = new THREE.BoxGeometry(0.08, 0.06, 0.06);
|
|
1206
|
+
const finderMaterial = new THREE.MeshStandardMaterial({
|
|
1207
|
+
color: 0x2d3748,
|
|
1208
|
+
metalness: 0.5,
|
|
1209
|
+
roughness: 0.4
|
|
1210
|
+
});
|
|
1211
|
+
const finder = new THREE.Mesh(finderGeometry, finderMaterial);
|
|
1212
|
+
finder.position.set(0, 0.13, -0.05);
|
|
1213
|
+
finder.castShadow = true;
|
|
1214
|
+
cameraGroup.add(finder);
|
|
1215
|
+
|
|
1216
|
+
cameraGroup.position.set(3.5, 1.3, 2.5);
|
|
1217
|
+
cameraGroup.rotation.y = -0.5;
|
|
1218
|
+
scene.add(cameraGroup);
|
|
1219
|
+
return cameraGroup;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const camera2 = createCamera();
|
|
1223
|
+
|
|
1224
|
+
// 可愛い猫ちゃん
|
|
1225
|
+
function createCat() {
|
|
1226
|
+
const catGroup = new THREE.Group();
|
|
1227
|
+
|
|
1228
|
+
// 体
|
|
1229
|
+
const bodyGeometry = new THREE.SphereGeometry(0.25, 16, 16);
|
|
1230
|
+
const bodyMaterial = new THREE.MeshStandardMaterial({
|
|
1231
|
+
color: 0xffd4e5,
|
|
1232
|
+
metalness: 0.1,
|
|
1233
|
+
roughness: 0.9
|
|
1234
|
+
});
|
|
1235
|
+
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
|
|
1236
|
+
body.scale.set(1.2, 1, 1);
|
|
1237
|
+
body.position.y = 0.2;
|
|
1238
|
+
body.castShadow = true;
|
|
1239
|
+
catGroup.add(body);
|
|
1240
|
+
|
|
1241
|
+
// 頭
|
|
1242
|
+
const headGeometry = new THREE.SphereGeometry(0.2, 16, 16);
|
|
1243
|
+
const headMaterial = new THREE.MeshStandardMaterial({
|
|
1244
|
+
color: 0xffd4e5,
|
|
1245
|
+
metalness: 0.1,
|
|
1246
|
+
roughness: 0.9
|
|
1247
|
+
});
|
|
1248
|
+
const head = new THREE.Mesh(headGeometry, headMaterial);
|
|
1249
|
+
head.position.set(0, 0.35, 0.2);
|
|
1250
|
+
head.castShadow = true;
|
|
1251
|
+
catGroup.add(head);
|
|
1252
|
+
|
|
1253
|
+
// 耳(2つ)
|
|
1254
|
+
const earGeometry = new THREE.ConeGeometry(0.08, 0.15, 8);
|
|
1255
|
+
const earMaterial = new THREE.MeshStandardMaterial({
|
|
1256
|
+
color: 0xffd4e5,
|
|
1257
|
+
metalness: 0.1,
|
|
1258
|
+
roughness: 0.9
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
const leftEar = new THREE.Mesh(earGeometry, earMaterial);
|
|
1262
|
+
leftEar.position.set(-0.12, 0.5, 0.2);
|
|
1263
|
+
leftEar.rotation.z = -0.3;
|
|
1264
|
+
leftEar.castShadow = true;
|
|
1265
|
+
catGroup.add(leftEar);
|
|
1266
|
+
|
|
1267
|
+
const rightEar = new THREE.Mesh(earGeometry, earMaterial);
|
|
1268
|
+
rightEar.position.set(0.12, 0.5, 0.2);
|
|
1269
|
+
rightEar.rotation.z = 0.3;
|
|
1270
|
+
rightEar.castShadow = true;
|
|
1271
|
+
catGroup.add(rightEar);
|
|
1272
|
+
|
|
1273
|
+
// 目(2つ)
|
|
1274
|
+
const eyeGeometry = new THREE.SphereGeometry(0.04, 8, 8);
|
|
1275
|
+
const eyeMaterial = new THREE.MeshStandardMaterial({
|
|
1276
|
+
color: 0x2d3748,
|
|
1277
|
+
metalness: 0.3,
|
|
1278
|
+
roughness: 0.5,
|
|
1279
|
+
emissive: 0x6366f1,
|
|
1280
|
+
emissiveIntensity: 0.5
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
|
|
1284
|
+
leftEye.position.set(-0.08, 0.38, 0.35);
|
|
1285
|
+
catGroup.add(leftEye);
|
|
1286
|
+
|
|
1287
|
+
const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial);
|
|
1288
|
+
rightEye.position.set(0.08, 0.38, 0.35);
|
|
1289
|
+
catGroup.add(rightEye);
|
|
1290
|
+
|
|
1291
|
+
// 鼻
|
|
1292
|
+
const noseGeometry = new THREE.SphereGeometry(0.02, 8, 8);
|
|
1293
|
+
const noseMaterial = new THREE.MeshStandardMaterial({
|
|
1294
|
+
color: 0xff99cc,
|
|
1295
|
+
metalness: 0.2,
|
|
1296
|
+
roughness: 0.8
|
|
1297
|
+
});
|
|
1298
|
+
const nose = new THREE.Mesh(noseGeometry, noseMaterial);
|
|
1299
|
+
nose.position.set(0, 0.33, 0.38);
|
|
1300
|
+
catGroup.add(nose);
|
|
1301
|
+
|
|
1302
|
+
// しっぽ
|
|
1303
|
+
const tailGeometry = new THREE.CylinderGeometry(0.04, 0.02, 0.4, 8);
|
|
1304
|
+
const tailMaterial = new THREE.MeshStandardMaterial({
|
|
1305
|
+
color: 0xffd4e5,
|
|
1306
|
+
metalness: 0.1,
|
|
1307
|
+
roughness: 0.9
|
|
1308
|
+
});
|
|
1309
|
+
const tail = new THREE.Mesh(tailGeometry, tailMaterial);
|
|
1310
|
+
tail.position.set(0, 0.3, -0.3);
|
|
1311
|
+
tail.rotation.x = -0.5;
|
|
1312
|
+
tail.castShadow = true;
|
|
1313
|
+
catGroup.add(tail);
|
|
1314
|
+
|
|
1315
|
+
// 足(4本)
|
|
1316
|
+
const legGeometry = new THREE.CylinderGeometry(0.04, 0.04, 0.15, 8);
|
|
1317
|
+
const legMaterial = new THREE.MeshStandardMaterial({
|
|
1318
|
+
color: 0xffd4e5,
|
|
1319
|
+
metalness: 0.1,
|
|
1320
|
+
roughness: 0.9
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
const legPositions = [
|
|
1324
|
+
[-0.15, 0.075, 0.15],
|
|
1325
|
+
[0.15, 0.075, 0.15],
|
|
1326
|
+
[-0.15, 0.075, -0.1],
|
|
1327
|
+
[0.15, 0.075, -0.1]
|
|
1328
|
+
];
|
|
1329
|
+
|
|
1330
|
+
legPositions.forEach(pos => {
|
|
1331
|
+
const leg = new THREE.Mesh(legGeometry, legMaterial);
|
|
1332
|
+
leg.position.set(pos[0], pos[1], pos[2]);
|
|
1333
|
+
leg.castShadow = true;
|
|
1334
|
+
catGroup.add(leg);
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
catGroup.position.set(1, 1.3, 0.5); // デスクの上に座る
|
|
1338
|
+
catGroup.userData.originalY = 1.3;
|
|
1339
|
+
catGroup.userData.tailAngle = 0;
|
|
1340
|
+
scene.add(catGroup);
|
|
1341
|
+
return catGroup;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const cat = createCat();
|
|
1345
|
+
|
|
1346
|
+
// 浮遊する風船
|
|
1347
|
+
function createBalloon(x, y, z, color) {
|
|
1348
|
+
const balloonGroup = new THREE.Group();
|
|
1349
|
+
|
|
1350
|
+
// 風船本体
|
|
1351
|
+
const balloonGeometry = new THREE.SphereGeometry(0.2, 16, 16);
|
|
1352
|
+
const balloonMaterial = new THREE.MeshStandardMaterial({
|
|
1353
|
+
color: color,
|
|
1354
|
+
metalness: 0.2,
|
|
1355
|
+
roughness: 0.6,
|
|
1356
|
+
emissive: color,
|
|
1357
|
+
emissiveIntensity: 0.4,
|
|
1358
|
+
transparent: true,
|
|
1359
|
+
opacity: 0.9
|
|
1360
|
+
});
|
|
1361
|
+
const balloon = new THREE.Mesh(balloonGeometry, balloonMaterial);
|
|
1362
|
+
balloon.scale.set(1, 1.2, 1);
|
|
1363
|
+
balloon.castShadow = true;
|
|
1364
|
+
balloonGroup.add(balloon);
|
|
1365
|
+
|
|
1366
|
+
// 結び目
|
|
1367
|
+
const knotGeometry = new THREE.SphereGeometry(0.04, 8, 8);
|
|
1368
|
+
const knotMaterial = new THREE.MeshStandardMaterial({
|
|
1369
|
+
color: color,
|
|
1370
|
+
metalness: 0.2,
|
|
1371
|
+
roughness: 0.7
|
|
1372
|
+
});
|
|
1373
|
+
const knot = new THREE.Mesh(knotGeometry, knotMaterial);
|
|
1374
|
+
knot.position.y = -0.24;
|
|
1375
|
+
balloonGroup.add(knot);
|
|
1376
|
+
|
|
1377
|
+
// 紐
|
|
1378
|
+
const stringGeometry = new THREE.CylinderGeometry(0.01, 0.01, 1, 8);
|
|
1379
|
+
const stringMaterial = new THREE.MeshStandardMaterial({
|
|
1380
|
+
color: 0xffffff,
|
|
1381
|
+
metalness: 0.1,
|
|
1382
|
+
roughness: 0.8
|
|
1383
|
+
});
|
|
1384
|
+
const string = new THREE.Mesh(stringGeometry, stringMaterial);
|
|
1385
|
+
string.position.y = -0.74;
|
|
1386
|
+
balloonGroup.add(string);
|
|
1387
|
+
|
|
1388
|
+
balloonGroup.position.set(x, y, z);
|
|
1389
|
+
balloonGroup.userData.originalY = y;
|
|
1390
|
+
balloonGroup.userData.floatSpeed = 0.5 + Math.random() * 0.5;
|
|
1391
|
+
balloonGroup.userData.floatPhase = Math.random() * Math.PI * 2;
|
|
1392
|
+
scene.add(balloonGroup);
|
|
1393
|
+
return balloonGroup;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
const balloons = [
|
|
1397
|
+
createBalloon(-1, 5, 1, 0xff99cc),
|
|
1398
|
+
createBalloon(2, 5.5, -1, 0x99ccff),
|
|
1399
|
+
createBalloon(-2.5, 6, -2, 0xccff99),
|
|
1400
|
+
createBalloon(4, 5.3, 0.5, 0xffcc99),
|
|
1401
|
+
createBalloon(0.5, 5.8, 3, 0xff99ff)
|
|
1402
|
+
];
|
|
1403
|
+
|
|
1404
|
+
// 星のモビール
|
|
1405
|
+
function createStarMobile() {
|
|
1406
|
+
const mobileGroup = new THREE.Group();
|
|
1407
|
+
|
|
1408
|
+
// 上部の棒
|
|
1409
|
+
const rodGeometry = new THREE.CylinderGeometry(0.02, 0.02, 1.5, 8);
|
|
1410
|
+
const rodMaterial = new THREE.MeshStandardMaterial({
|
|
1411
|
+
color: 0xffd4a3,
|
|
1412
|
+
metalness: 0.5,
|
|
1413
|
+
roughness: 0.4
|
|
1414
|
+
});
|
|
1415
|
+
const rod = new THREE.Mesh(rodGeometry, rodMaterial);
|
|
1416
|
+
rod.rotation.z = Math.PI / 2;
|
|
1417
|
+
mobileGroup.add(rod);
|
|
1418
|
+
|
|
1419
|
+
// 吊り紐
|
|
1420
|
+
const hangGeometry = new THREE.CylinderGeometry(0.01, 0.01, 0.5, 8);
|
|
1421
|
+
const hangMaterial = new THREE.MeshStandardMaterial({
|
|
1422
|
+
color: 0xa0826d,
|
|
1423
|
+
metalness: 0.1,
|
|
1424
|
+
roughness: 0.9
|
|
1425
|
+
});
|
|
1426
|
+
const hang = new THREE.Mesh(hangGeometry, hangMaterial);
|
|
1427
|
+
hang.position.y = 0.25;
|
|
1428
|
+
mobileGroup.add(hang);
|
|
1429
|
+
|
|
1430
|
+
// 星(3つ)
|
|
1431
|
+
const starColors = [0xffeb3b, 0xff99cc, 0x99ccff];
|
|
1432
|
+
const starPositions = [[-0.6, -0.3], [0, -0.5], [0.6, -0.3]];
|
|
1433
|
+
|
|
1434
|
+
starPositions.forEach((pos, index) => {
|
|
1435
|
+
// 星の紐
|
|
1436
|
+
const starStringGeometry = new THREE.CylinderGeometry(0.01, 0.01, 0.4, 8);
|
|
1437
|
+
const starString = new THREE.Mesh(starStringGeometry, hangMaterial);
|
|
1438
|
+
starString.position.set(pos[0], pos[1], 0);
|
|
1439
|
+
mobileGroup.add(starString);
|
|
1440
|
+
|
|
1441
|
+
// 星本体
|
|
1442
|
+
const starGeometry = new THREE.SphereGeometry(0.12, 8, 8);
|
|
1443
|
+
const starMaterial = new THREE.MeshStandardMaterial({
|
|
1444
|
+
color: starColors[index],
|
|
1445
|
+
metalness: 0.2,
|
|
1446
|
+
roughness: 0.5,
|
|
1447
|
+
emissive: starColors[index],
|
|
1448
|
+
emissiveIntensity: 0.6
|
|
1449
|
+
});
|
|
1450
|
+
const star = new THREE.Mesh(starGeometry, starMaterial);
|
|
1451
|
+
star.position.set(pos[0], pos[1] - 0.2, 0);
|
|
1452
|
+
star.castShadow = true;
|
|
1453
|
+
mobileGroup.add(star);
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
mobileGroup.position.set(0, 7, 1);
|
|
1457
|
+
mobileGroup.userData.rotationSpeed = 0.2;
|
|
1458
|
+
scene.add(mobileGroup);
|
|
1459
|
+
return mobileGroup;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const starMobile = createStarMobile();
|
|
1463
|
+
|
|
1464
|
+
// デスクランプ
|
|
1465
|
+
function createDeskLamp() {
|
|
1466
|
+
const lampGroup = new THREE.Group();
|
|
1467
|
+
|
|
1468
|
+
// ベース
|
|
1469
|
+
const baseGeometry = new THREE.CylinderGeometry(0.15, 0.15, 0.05, 16);
|
|
1470
|
+
const baseMaterial = new THREE.MeshStandardMaterial({
|
|
1471
|
+
color: 0xffd4e5,
|
|
1472
|
+
metalness: 0.4,
|
|
1473
|
+
roughness: 0.5
|
|
1474
|
+
});
|
|
1475
|
+
const base = new THREE.Mesh(baseGeometry, baseMaterial);
|
|
1476
|
+
base.position.y = 0.025;
|
|
1477
|
+
base.castShadow = true;
|
|
1478
|
+
lampGroup.add(base);
|
|
1479
|
+
|
|
1480
|
+
// アーム
|
|
1481
|
+
const armGeometry = new THREE.CylinderGeometry(0.03, 0.03, 0.8, 8);
|
|
1482
|
+
const armMaterial = new THREE.MeshStandardMaterial({
|
|
1483
|
+
color: 0xffd4e5,
|
|
1484
|
+
metalness: 0.5,
|
|
1485
|
+
roughness: 0.4
|
|
1486
|
+
});
|
|
1487
|
+
const arm = new THREE.Mesh(armGeometry, armMaterial);
|
|
1488
|
+
arm.position.set(0, 0.4, 0);
|
|
1489
|
+
arm.rotation.z = Math.PI / 6;
|
|
1490
|
+
arm.castShadow = true;
|
|
1491
|
+
lampGroup.add(arm);
|
|
1492
|
+
|
|
1493
|
+
// シェード
|
|
1494
|
+
const shadeGeometry = new THREE.ConeGeometry(0.2, 0.3, 16);
|
|
1495
|
+
const shadeMaterial = new THREE.MeshStandardMaterial({
|
|
1496
|
+
color: 0xffd4e5,
|
|
1497
|
+
metalness: 0.3,
|
|
1498
|
+
roughness: 0.5
|
|
1499
|
+
});
|
|
1500
|
+
const shade = new THREE.Mesh(shadeGeometry, shadeMaterial);
|
|
1501
|
+
shade.position.set(0.35, 0.75, 0);
|
|
1502
|
+
shade.rotation.z = Math.PI;
|
|
1503
|
+
shade.castShadow = true;
|
|
1504
|
+
lampGroup.add(shade);
|
|
1505
|
+
|
|
1506
|
+
// 光源
|
|
1507
|
+
const light = new THREE.PointLight(0xfff5e6, 1.5, 5);
|
|
1508
|
+
light.position.set(0.35, 0.6, 0);
|
|
1509
|
+
light.castShadow = true;
|
|
1510
|
+
lampGroup.add(light);
|
|
1511
|
+
|
|
1512
|
+
lampGroup.position.set(1, 1.25, 1.5);
|
|
1513
|
+
lampGroup.userData.light = light;
|
|
1514
|
+
scene.add(lampGroup);
|
|
1515
|
+
return lampGroup;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const deskLamp = createDeskLamp();
|
|
1519
|
+
|
|
1520
|
+
// フロアランプ
|
|
1521
|
+
function createFloorLamp() {
|
|
1522
|
+
const lampGroup = new THREE.Group();
|
|
1523
|
+
|
|
1524
|
+
// ポール
|
|
1525
|
+
const poleGeometry = new THREE.CylinderGeometry(0.05, 0.05, 2, 16);
|
|
1526
|
+
const poleMaterial = new THREE.MeshStandardMaterial({
|
|
1527
|
+
color: 0xe8c896,
|
|
1528
|
+
metalness: 0.6,
|
|
1529
|
+
roughness: 0.3
|
|
1530
|
+
});
|
|
1531
|
+
const pole = new THREE.Mesh(poleGeometry, poleMaterial);
|
|
1532
|
+
pole.position.y = 1;
|
|
1533
|
+
pole.castShadow = true;
|
|
1534
|
+
lampGroup.add(pole);
|
|
1535
|
+
|
|
1536
|
+
// シェード
|
|
1537
|
+
const shadeGeometry = new THREE.SphereGeometry(0.4, 16, 16);
|
|
1538
|
+
const shadeMaterial = new THREE.MeshStandardMaterial({
|
|
1539
|
+
color: 0xfff5f0,
|
|
1540
|
+
metalness: 0.1,
|
|
1541
|
+
roughness: 0.5,
|
|
1542
|
+
emissive: 0xfff5e6,
|
|
1543
|
+
emissiveIntensity: 0.4
|
|
1544
|
+
});
|
|
1545
|
+
const shade = new THREE.Mesh(shadeGeometry, shadeMaterial);
|
|
1546
|
+
shade.position.y = 2;
|
|
1547
|
+
shade.castShadow = true;
|
|
1548
|
+
lampGroup.add(shade);
|
|
1549
|
+
|
|
1550
|
+
// 光源
|
|
1551
|
+
const light = new THREE.PointLight(0xfff5e6, 2, 8);
|
|
1552
|
+
light.position.y = 2;
|
|
1553
|
+
light.castShadow = true;
|
|
1554
|
+
lampGroup.add(light);
|
|
1555
|
+
|
|
1556
|
+
lampGroup.position.set(-4.5, 0, 2);
|
|
1557
|
+
lampGroup.userData.light = light;
|
|
1558
|
+
scene.add(lampGroup);
|
|
1559
|
+
return lampGroup;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const floorLamp = createFloorLamp();
|
|
1563
|
+
|
|
1564
|
+
// 小物:マグカップ
|
|
1565
|
+
function createMug() {
|
|
1566
|
+
const mugGroup = new THREE.Group();
|
|
1567
|
+
|
|
1568
|
+
// カップ本体
|
|
1569
|
+
const cupGeometry = new THREE.CylinderGeometry(0.15, 0.12, 0.25, 16);
|
|
1570
|
+
const cupMaterial = new THREE.MeshStandardMaterial({
|
|
1571
|
+
color: 0xffc4d6,
|
|
1572
|
+
metalness: 0.3,
|
|
1573
|
+
roughness: 0.6
|
|
1574
|
+
});
|
|
1575
|
+
const cup = new THREE.Mesh(cupGeometry, cupMaterial);
|
|
1576
|
+
cup.position.y = 0.125;
|
|
1577
|
+
cup.castShadow = true;
|
|
1578
|
+
mugGroup.add(cup);
|
|
1579
|
+
|
|
1580
|
+
// ハンドル
|
|
1581
|
+
const handleGeometry = new THREE.TorusGeometry(0.12, 0.03, 8, 16, Math.PI);
|
|
1582
|
+
const handleMaterial = new THREE.MeshStandardMaterial({
|
|
1583
|
+
color: 0xffc4d6,
|
|
1584
|
+
metalness: 0.3,
|
|
1585
|
+
roughness: 0.6
|
|
1586
|
+
});
|
|
1587
|
+
const handle = new THREE.Mesh(handleGeometry, handleMaterial);
|
|
1588
|
+
handle.position.set(0.15, 0.125, 0);
|
|
1589
|
+
handle.rotation.y = Math.PI / 2;
|
|
1590
|
+
handle.castShadow = true;
|
|
1591
|
+
mugGroup.add(handle);
|
|
1592
|
+
|
|
1593
|
+
mugGroup.position.set(3.2, 1.3, 1.8);
|
|
1594
|
+
scene.add(mugGroup);
|
|
1595
|
+
return mugGroup;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const mug = createMug();
|
|
1599
|
+
|
|
1600
|
+
// パーティクル(キラキラした光の粒子)強化版
|
|
1601
|
+
function createDustParticles() {
|
|
1602
|
+
const particleCount = 400; // 倍増
|
|
1603
|
+
const geometry = new THREE.BufferGeometry();
|
|
1604
|
+
const positions = [];
|
|
1605
|
+
const colors = [];
|
|
1606
|
+
|
|
1607
|
+
for (let i = 0; i < particleCount; i++) {
|
|
1608
|
+
positions.push(
|
|
1609
|
+
(Math.random() - 0.5) * 20,
|
|
1610
|
+
Math.random() * 8,
|
|
1611
|
+
(Math.random() - 0.5) * 20
|
|
1612
|
+
);
|
|
1613
|
+
|
|
1614
|
+
// カラフルなパーティクル
|
|
1615
|
+
const hue = Math.random();
|
|
1616
|
+
const color = new THREE.Color().setHSL(hue, 0.8, 0.8);
|
|
1617
|
+
colors.push(color.r, color.g, color.b);
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
1621
|
+
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
1622
|
+
|
|
1623
|
+
// 丸いテクスチャを作成
|
|
1624
|
+
const canvas = document.createElement('canvas');
|
|
1625
|
+
canvas.width = 32;
|
|
1626
|
+
canvas.height = 32;
|
|
1627
|
+
const ctx = canvas.getContext('2d');
|
|
1628
|
+
const gradient = ctx.createRadialGradient(16, 16, 0, 16, 16, 16);
|
|
1629
|
+
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
|
|
1630
|
+
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)');
|
|
1631
|
+
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
1632
|
+
ctx.fillStyle = gradient;
|
|
1633
|
+
ctx.fillRect(0, 0, 32, 32);
|
|
1634
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
1635
|
+
|
|
1636
|
+
const material = new THREE.PointsMaterial({
|
|
1637
|
+
size: 0.2, // 少し大きく
|
|
1638
|
+
vertexColors: true,
|
|
1639
|
+
transparent: true,
|
|
1640
|
+
opacity: 0.7, // より明るく
|
|
1641
|
+
blending: THREE.AdditiveBlending,
|
|
1642
|
+
map: texture, // 丸いテクスチャを適用
|
|
1643
|
+
sizeAttenuation: true
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
const particles = new THREE.Points(geometry, material);
|
|
1647
|
+
scene.add(particles);
|
|
1648
|
+
return particles;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
const dustParticles = createDustParticles();
|
|
1652
|
+
|
|
1653
|
+
// 星空エフェクト
|
|
1654
|
+
function createStarField() {
|
|
1655
|
+
const starCount = 150;
|
|
1656
|
+
const geometry = new THREE.BufferGeometry();
|
|
1657
|
+
const positions = [];
|
|
1658
|
+
const colors = [];
|
|
1659
|
+
|
|
1660
|
+
for (let i = 0; i < starCount; i++) {
|
|
1661
|
+
// 天井付近に星を配置
|
|
1662
|
+
positions.push(
|
|
1663
|
+
(Math.random() - 0.5) * 30,
|
|
1664
|
+
7 + Math.random() * 2,
|
|
1665
|
+
(Math.random() - 0.5) * 30
|
|
1666
|
+
);
|
|
1667
|
+
|
|
1668
|
+
const color = new THREE.Color().setHSL(0.6 + Math.random() * 0.2, 0.8, 0.9);
|
|
1669
|
+
colors.push(color.r, color.g, color.b);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
1673
|
+
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
1674
|
+
|
|
1675
|
+
const material = new THREE.PointsMaterial({
|
|
1676
|
+
size: 0.15,
|
|
1677
|
+
vertexColors: true,
|
|
1678
|
+
transparent: true,
|
|
1679
|
+
opacity: 0.9,
|
|
1680
|
+
blending: THREE.AdditiveBlending
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
const stars = new THREE.Points(geometry, material);
|
|
1684
|
+
scene.add(stars);
|
|
1685
|
+
return stars;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
const starField = createStarField();
|
|
1689
|
+
|
|
1690
|
+
// オーロラ効果(天井)
|
|
1691
|
+
function createAurora() {
|
|
1692
|
+
const auroraGroup = new THREE.Group();
|
|
1693
|
+
|
|
1694
|
+
// 複数のオーロラレイヤー
|
|
1695
|
+
for (let i = 0; i < 3; i++) {
|
|
1696
|
+
const geometry = new THREE.PlaneGeometry(12, 3, 32, 8);
|
|
1697
|
+
const vertices = geometry.attributes.position.array;
|
|
1698
|
+
|
|
1699
|
+
// 波打つ形状を作る
|
|
1700
|
+
for (let j = 0; j < vertices.length; j += 3) {
|
|
1701
|
+
vertices[j + 2] = Math.sin(vertices[j] * 0.5 + i) * 0.3;
|
|
1702
|
+
}
|
|
1703
|
+
geometry.attributes.position.needsUpdate = true;
|
|
1704
|
+
|
|
1705
|
+
const material = new THREE.MeshBasicMaterial({
|
|
1706
|
+
color: new THREE.Color().setHSL(0.5 + i * 0.1, 0.8, 0.6),
|
|
1707
|
+
transparent: true,
|
|
1708
|
+
opacity: 0.15 + i * 0.05,
|
|
1709
|
+
side: THREE.DoubleSide,
|
|
1710
|
+
blending: THREE.AdditiveBlending,
|
|
1711
|
+
depthWrite: false
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
const aurora = new THREE.Mesh(geometry, material);
|
|
1715
|
+
aurora.position.set(0, 7.5 - i * 0.2, 0);
|
|
1716
|
+
aurora.rotation.x = Math.PI / 2;
|
|
1717
|
+
aurora.userData.offset = i * 2;
|
|
1718
|
+
auroraGroup.add(aurora);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
scene.add(auroraGroup);
|
|
1722
|
+
return auroraGroup;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
const aurora = createAurora();
|
|
1726
|
+
|
|
1727
|
+
// 浮遊する魔法の球体
|
|
1728
|
+
function createFloatingOrbs() {
|
|
1729
|
+
const orbs = [];
|
|
1730
|
+
const orbCount = 8;
|
|
1731
|
+
|
|
1732
|
+
for (let i = 0; i < orbCount; i++) {
|
|
1733
|
+
const geometry = new THREE.SphereGeometry(0.15, 16, 16);
|
|
1734
|
+
const material = new THREE.MeshBasicMaterial({
|
|
1735
|
+
color: new THREE.Color().setHSL(i / orbCount, 0.9, 0.7),
|
|
1736
|
+
transparent: true,
|
|
1737
|
+
opacity: 0.6,
|
|
1738
|
+
blending: THREE.AdditiveBlending
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
const orb = new THREE.Mesh(geometry, material);
|
|
1742
|
+
|
|
1743
|
+
// ランダムな軌道パラメータ
|
|
1744
|
+
const radius = 3 + Math.random() * 2;
|
|
1745
|
+
const height = 2 + Math.random() * 4;
|
|
1746
|
+
const speed = 0.3 + Math.random() * 0.5;
|
|
1747
|
+
const phase = (i / orbCount) * Math.PI * 2;
|
|
1748
|
+
|
|
1749
|
+
orb.userData = {
|
|
1750
|
+
radius: radius,
|
|
1751
|
+
height: height,
|
|
1752
|
+
speed: speed,
|
|
1753
|
+
phase: phase,
|
|
1754
|
+
verticalPhase: Math.random() * Math.PI * 2
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
// グロー効果用の外側の球
|
|
1758
|
+
const glowGeometry = new THREE.SphereGeometry(0.25, 16, 16);
|
|
1759
|
+
const glowMaterial = new THREE.MeshBasicMaterial({
|
|
1760
|
+
color: new THREE.Color().setHSL(i / orbCount, 0.9, 0.5),
|
|
1761
|
+
transparent: true,
|
|
1762
|
+
opacity: 0.2,
|
|
1763
|
+
blending: THREE.AdditiveBlending,
|
|
1764
|
+
depthWrite: false
|
|
1765
|
+
});
|
|
1766
|
+
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
|
|
1767
|
+
orb.add(glow);
|
|
1768
|
+
|
|
1769
|
+
scene.add(orb);
|
|
1770
|
+
orbs.push(orb);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
return orbs;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
const floatingOrbs = createFloatingOrbs();
|
|
1777
|
+
|
|
1778
|
+
// 魔法のポータル
|
|
1779
|
+
function createMagicPortal() {
|
|
1780
|
+
const portalGroup = new THREE.Group();
|
|
1781
|
+
|
|
1782
|
+
// ポータルリング(複数の回転するリング)
|
|
1783
|
+
for (let i = 0; i < 4; i++) {
|
|
1784
|
+
const ringGeometry = new THREE.TorusGeometry(1.2 - i * 0.15, 0.05, 16, 100);
|
|
1785
|
+
const ringMaterial = new THREE.MeshBasicMaterial({
|
|
1786
|
+
color: new THREE.Color().setHSL(0.6 + i * 0.1, 1, 0.6),
|
|
1787
|
+
transparent: true,
|
|
1788
|
+
opacity: 0.8 - i * 0.15,
|
|
1789
|
+
blending: THREE.AdditiveBlending,
|
|
1790
|
+
depthWrite: false
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
|
|
1794
|
+
ring.userData.rotationSpeed = 0.5 + i * 0.3;
|
|
1795
|
+
ring.userData.direction = i % 2 === 0 ? 1 : -1;
|
|
1796
|
+
portalGroup.add(ring);
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// ポータル中心の光
|
|
1800
|
+
const centerGeometry = new THREE.CircleGeometry(1, 32);
|
|
1801
|
+
const centerMaterial = new THREE.MeshBasicMaterial({
|
|
1802
|
+
color: 0x88ccff,
|
|
1803
|
+
transparent: true,
|
|
1804
|
+
opacity: 0.3,
|
|
1805
|
+
blending: THREE.AdditiveBlending,
|
|
1806
|
+
depthWrite: false,
|
|
1807
|
+
side: THREE.DoubleSide
|
|
1808
|
+
});
|
|
1809
|
+
const center = new THREE.Mesh(centerGeometry, centerMaterial);
|
|
1810
|
+
portalGroup.add(center);
|
|
1811
|
+
|
|
1812
|
+
portalGroup.position.set(-5, 4, -2);
|
|
1813
|
+
portalGroup.rotation.y = Math.PI / 4;
|
|
1814
|
+
scene.add(portalGroup);
|
|
1815
|
+
return portalGroup;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
const magicPortal = createMagicPortal();
|
|
1819
|
+
|
|
1820
|
+
// ホログラフィック音楽ビジュアライザー
|
|
1821
|
+
function createHolographicVisualizer() {
|
|
1822
|
+
const visualizerGroup = new THREE.Group();
|
|
1823
|
+
|
|
1824
|
+
// 複数の球体
|
|
1825
|
+
const bars = [];
|
|
1826
|
+
const barCount = 16;
|
|
1827
|
+
|
|
1828
|
+
for (let i = 0; i < barCount; i++) {
|
|
1829
|
+
// 球体に変更
|
|
1830
|
+
const geometry = new THREE.SphereGeometry(0.15, 16, 16);
|
|
1831
|
+
const material = new THREE.MeshBasicMaterial({
|
|
1832
|
+
color: new THREE.Color().setHSL(i / barCount, 0.9, 0.6),
|
|
1833
|
+
transparent: true,
|
|
1834
|
+
opacity: 0.7,
|
|
1835
|
+
blending: THREE.AdditiveBlending,
|
|
1836
|
+
depthWrite: false
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
const bar = new THREE.Mesh(geometry, material);
|
|
1840
|
+
const angle = (i / barCount) * Math.PI * 2;
|
|
1841
|
+
const radius = 0.8;
|
|
1842
|
+
bar.position.x = Math.cos(angle) * radius;
|
|
1843
|
+
bar.position.z = Math.sin(angle) * radius;
|
|
1844
|
+
bar.position.y = 0.5;
|
|
1845
|
+
|
|
1846
|
+
bar.userData.index = i;
|
|
1847
|
+
bar.userData.baseScale = 1;
|
|
1848
|
+
|
|
1849
|
+
visualizerGroup.add(bar);
|
|
1850
|
+
bars.push(bar);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
visualizerGroup.position.set(2, 1.5, -1);
|
|
1854
|
+
visualizerGroup.userData.bars = bars;
|
|
1855
|
+
scene.add(visualizerGroup);
|
|
1856
|
+
return visualizerGroup;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const holographicVisualizer = createHolographicVisualizer();
|
|
1860
|
+
|
|
1861
|
+
// 虹色のアンビエントライト(音楽連動)
|
|
1862
|
+
const rainbowLight = new THREE.PointLight(0xff99cc, 0, 10);
|
|
1863
|
+
rainbowLight.position.set(0, 5, 0);
|
|
1864
|
+
scene.add(rainbowLight);
|
|
1865
|
+
|
|
1866
|
+
// フェアリーライト(壁のイルミネーション)
|
|
1867
|
+
function createFairyLights(startPos, endPos, numLights = 15) {
|
|
1868
|
+
const lightsGroup = new THREE.Group();
|
|
1869
|
+
|
|
1870
|
+
for (let i = 0; i < numLights; i++) {
|
|
1871
|
+
const t = i / (numLights - 1);
|
|
1872
|
+
const x = startPos.x + (endPos.x - startPos.x) * t;
|
|
1873
|
+
const y = startPos.y + (endPos.y - startPos.y) * t + Math.sin(t * Math.PI * 2) * 0.3;
|
|
1874
|
+
const z = startPos.z + (endPos.z - startPos.z) * t;
|
|
1875
|
+
|
|
1876
|
+
// ライトの球体
|
|
1877
|
+
const lightGeometry = new THREE.SphereGeometry(0.08, 16, 16);
|
|
1878
|
+
const lightMaterial = new THREE.MeshStandardMaterial({
|
|
1879
|
+
color: new THREE.Color().setHSL(Math.random(), 0.8, 0.7),
|
|
1880
|
+
emissive: new THREE.Color().setHSL(Math.random(), 0.8, 0.7),
|
|
1881
|
+
emissiveIntensity: 1.5,
|
|
1882
|
+
transparent: true,
|
|
1883
|
+
opacity: 0.9
|
|
1884
|
+
});
|
|
1885
|
+
const lightBulb = new THREE.Mesh(lightGeometry, lightMaterial);
|
|
1886
|
+
lightBulb.position.set(x, y, z);
|
|
1887
|
+
|
|
1888
|
+
// 点光源
|
|
1889
|
+
const pointLight = new THREE.PointLight(
|
|
1890
|
+
new THREE.Color().setHSL(Math.random(), 0.8, 0.7),
|
|
1891
|
+
0.8,
|
|
1892
|
+
2
|
|
1893
|
+
);
|
|
1894
|
+
pointLight.position.set(x, y, z);
|
|
1895
|
+
|
|
1896
|
+
lightsGroup.add(lightBulb);
|
|
1897
|
+
lightsGroup.add(pointLight);
|
|
1898
|
+
|
|
1899
|
+
// アニメーション用データを保存
|
|
1900
|
+
lightBulb.userData = {
|
|
1901
|
+
originalIntensity: 1.5,
|
|
1902
|
+
phase: Math.random() * Math.PI * 2
|
|
1903
|
+
};
|
|
1904
|
+
pointLight.userData = {
|
|
1905
|
+
originalIntensity: 0.8,
|
|
1906
|
+
phase: Math.random() * Math.PI * 2
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
scene.add(lightsGroup);
|
|
1911
|
+
return lightsGroup;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// 壁沿いにフェアリーライトを配置
|
|
1915
|
+
const fairyLights1 = createFairyLights(
|
|
1916
|
+
{ x: -5.9, y: 6, z: -5 },
|
|
1917
|
+
{ x: 5.9, y: 6, z: -5 },
|
|
1918
|
+
20
|
|
1919
|
+
);
|
|
1920
|
+
const fairyLights2 = createFairyLights(
|
|
1921
|
+
{ x: -5.9, y: 6, z: -5 },
|
|
1922
|
+
{ x: -5.9, y: 6, z: 5 },
|
|
1923
|
+
15
|
|
1924
|
+
);
|
|
1925
|
+
|
|
1926
|
+
// 壁のポスター/アートフレーム
|
|
1927
|
+
function createWallArt(x, y, z, rotation, color, width = 1, height = 1.2) {
|
|
1928
|
+
const artGroup = new THREE.Group();
|
|
1929
|
+
|
|
1930
|
+
// フレーム
|
|
1931
|
+
const frameGeometry = new THREE.BoxGeometry(width + 0.1, height + 0.1, 0.05);
|
|
1932
|
+
const frameMaterial = new THREE.MeshStandardMaterial({
|
|
1933
|
+
color: 0xffd4a3,
|
|
1934
|
+
metalness: 0.3,
|
|
1935
|
+
roughness: 0.6
|
|
1936
|
+
});
|
|
1937
|
+
const frame = new THREE.Mesh(frameGeometry, frameMaterial);
|
|
1938
|
+
frame.castShadow = true;
|
|
1939
|
+
artGroup.add(frame);
|
|
1940
|
+
|
|
1941
|
+
// アート本体
|
|
1942
|
+
const artGeometry = new THREE.BoxGeometry(width, height, 0.02);
|
|
1943
|
+
const artMaterial = new THREE.MeshStandardMaterial({
|
|
1944
|
+
color: color,
|
|
1945
|
+
metalness: 0.1,
|
|
1946
|
+
roughness: 0.7,
|
|
1947
|
+
emissive: color,
|
|
1948
|
+
emissiveIntensity: 0.2
|
|
1949
|
+
});
|
|
1950
|
+
const art = new THREE.Mesh(artGeometry, artMaterial);
|
|
1951
|
+
art.position.z = 0.035;
|
|
1952
|
+
art.castShadow = true;
|
|
1953
|
+
artGroup.add(art);
|
|
1954
|
+
|
|
1955
|
+
artGroup.position.set(x, y, z);
|
|
1956
|
+
artGroup.rotation.y = rotation;
|
|
1957
|
+
scene.add(artGroup);
|
|
1958
|
+
return artGroup;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// 背面の壁にポスターを配置
|
|
1962
|
+
const poster1 = createWallArt(-2, 4.5, -5.95, 0, 0xff99cc, 0.8, 1);
|
|
1963
|
+
const poster2 = createWallArt(0.5, 4.5, -5.95, 0, 0x99ccff, 0.8, 1);
|
|
1964
|
+
const poster3 = createWallArt(3, 4.5, -5.95, 0, 0xccff99, 0.8, 1);
|
|
1965
|
+
|
|
1966
|
+
// 左の壁にもポスター
|
|
1967
|
+
const poster4 = createWallArt(-5.95, 4.5, -2, Math.PI / 2, 0xffcc99, 0.8, 1);
|
|
1968
|
+
|
|
1969
|
+
// カメラ位置(アイソメトリック風)
|
|
1970
|
+
camera.position.set(8, 10, 12);
|
|
1971
|
+
camera.lookAt(0, 2, 0);
|
|
1972
|
+
|
|
1973
|
+
// Controls
|
|
1974
|
+
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
|
1975
|
+
controls.enableDamping = true;
|
|
1976
|
+
controls.dampingFactor = 0.05;
|
|
1977
|
+
controls.maxPolarAngle = Math.PI / 2.2;
|
|
1978
|
+
controls.target.set(0, 2, 0);
|
|
1979
|
+
|
|
1980
|
+
// Initialize ChocoDrop
|
|
1981
|
+
const chocoDrop = ChocoDrop.createChocoDrop(scene, {
|
|
1982
|
+
camera: camera,
|
|
1983
|
+
renderer: renderer,
|
|
1984
|
+
serverUrl: null,
|
|
1985
|
+
enableServerHealthCheck: false,
|
|
1986
|
+
sceneOptions: {
|
|
1987
|
+
enableMouseInteraction: true
|
|
1988
|
+
},
|
|
1989
|
+
onControlsToggle: (disabled) => {
|
|
1990
|
+
controls.enabled = !disabled;
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1994
|
+
// 音響システム初期化
|
|
1995
|
+
let audioContext = null;
|
|
1996
|
+
let analyser = null;
|
|
1997
|
+
let dataArray = null;
|
|
1998
|
+
let beatDetectionHistory = [];
|
|
1999
|
+
let lastBeatTime = 0;
|
|
2000
|
+
let musicData = {
|
|
2001
|
+
volume: 0,
|
|
2002
|
+
bass: 0,
|
|
2003
|
+
mid: 0,
|
|
2004
|
+
treble: 0,
|
|
2005
|
+
bpm: 0,
|
|
2006
|
+
beatDetected: false,
|
|
2007
|
+
isPlaying: false
|
|
2008
|
+
};
|
|
2009
|
+
const audioStatusEl = document.getElementById('audio-status');
|
|
2010
|
+
const audioTextEl = document.getElementById('audio-text');
|
|
2011
|
+
|
|
2012
|
+
function setupAudioAnalysis() {
|
|
2013
|
+
if (!audioContext) {
|
|
2014
|
+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
2015
|
+
analyser = audioContext.createAnalyser();
|
|
2016
|
+
analyser.fftSize = 256;
|
|
2017
|
+
dataArray = new Uint8Array(analyser.frequencyBinCount);
|
|
2018
|
+
console.log('🎵 Audio analysis initialized');
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function connectVideoToAudio(videoElement) {
|
|
2023
|
+
if (!audioContext || !analyser) {
|
|
2024
|
+
setupAudioAnalysis();
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
try {
|
|
2028
|
+
if (audioContext.state === 'suspended') {
|
|
2029
|
+
audioContext.resume();
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
const source = audioContext.createMediaElementSource(videoElement);
|
|
2033
|
+
source.connect(analyser);
|
|
2034
|
+
analyser.connect(audioContext.destination);
|
|
2035
|
+
|
|
2036
|
+
musicData.isPlaying = true;
|
|
2037
|
+
if (audioStatusEl) {
|
|
2038
|
+
audioStatusEl.classList.add('active');
|
|
2039
|
+
if (audioTextEl) {
|
|
2040
|
+
audioTextEl.textContent = '音響連動中 ♪';
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
console.log('🎵 Video connected to audio analyser');
|
|
2044
|
+
} catch (error) {
|
|
2045
|
+
console.warn('Audio connection error:', error);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
function analyzeMusicData() {
|
|
2050
|
+
if (!analyser || !dataArray) return;
|
|
2051
|
+
|
|
2052
|
+
analyser.getByteFrequencyData(dataArray);
|
|
2053
|
+
|
|
2054
|
+
let sum = 0;
|
|
2055
|
+
for (let i = 0; i < dataArray.length; i++) {
|
|
2056
|
+
sum += dataArray[i];
|
|
2057
|
+
}
|
|
2058
|
+
musicData.volume = sum / dataArray.length / 255;
|
|
2059
|
+
|
|
2060
|
+
const bassRange = Math.floor(dataArray.length * 0.1);
|
|
2061
|
+
const midRange = Math.floor(dataArray.length * 0.5);
|
|
2062
|
+
|
|
2063
|
+
let bassSum = 0, midSum = 0, trebleSum = 0;
|
|
2064
|
+
|
|
2065
|
+
for (let i = 0; i < bassRange; i++) {
|
|
2066
|
+
bassSum += dataArray[i];
|
|
2067
|
+
}
|
|
2068
|
+
for (let i = bassRange; i < midRange; i++) {
|
|
2069
|
+
midSum += dataArray[i];
|
|
2070
|
+
}
|
|
2071
|
+
for (let i = midRange; i < dataArray.length; i++) {
|
|
2072
|
+
trebleSum += dataArray[i];
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
musicData.bass = bassSum / bassRange / 255;
|
|
2076
|
+
musicData.mid = midSum / (midRange - bassRange) / 255;
|
|
2077
|
+
musicData.treble = trebleSum / (dataArray.length - midRange) / 255;
|
|
2078
|
+
|
|
2079
|
+
const currentTime = performance.now();
|
|
2080
|
+
musicData.beatDetected = false;
|
|
2081
|
+
|
|
2082
|
+
if (musicData.bass > 0.3 && musicData.volume > 0.2) {
|
|
2083
|
+
if (currentTime - lastBeatTime > 300) {
|
|
2084
|
+
musicData.beatDetected = true;
|
|
2085
|
+
lastBeatTime = currentTime;
|
|
2086
|
+
|
|
2087
|
+
beatDetectionHistory.push(currentTime);
|
|
2088
|
+
if (beatDetectionHistory.length > 8) {
|
|
2089
|
+
beatDetectionHistory.shift();
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
if (beatDetectionHistory.length >= 4) {
|
|
2093
|
+
const intervals = [];
|
|
2094
|
+
for (let i = 1; i < beatDetectionHistory.length; i++) {
|
|
2095
|
+
intervals.push(beatDetectionHistory[i] - beatDetectionHistory[i-1]);
|
|
2096
|
+
}
|
|
2097
|
+
const avgInterval = intervals.reduce((a, b) => a + b) / intervals.length;
|
|
2098
|
+
musicData.bpm = Math.round(60000 / avgInterval);
|
|
2099
|
+
|
|
2100
|
+
if (audioTextEl && musicData.bpm > 0) {
|
|
2101
|
+
audioTextEl.textContent = `音響連動中 ♪ (${musicData.bpm} BPM)`;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function observeVideoElements() {
|
|
2109
|
+
const videoObserver = new MutationObserver((mutations) => {
|
|
2110
|
+
mutations.forEach((mutation) => {
|
|
2111
|
+
mutation.addedNodes.forEach((node) => {
|
|
2112
|
+
if (node.nodeName === 'VIDEO') {
|
|
2113
|
+
console.log('🎬 Video element detected');
|
|
2114
|
+
connectVideoToAudio(node);
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
});
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
videoObserver.observe(document.body, {
|
|
2121
|
+
childList: true,
|
|
2122
|
+
subtree: true
|
|
2123
|
+
});
|
|
2124
|
+
|
|
2125
|
+
const existingVideos = document.querySelectorAll('video');
|
|
2126
|
+
existingVideos.forEach(video => {
|
|
2127
|
+
connectVideoToAudio(video);
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
if (audioStatusEl) {
|
|
2132
|
+
audioStatusEl.classList.add('active');
|
|
2133
|
+
}
|
|
2134
|
+
observeVideoElements();
|
|
2135
|
+
|
|
2136
|
+
// Animation loop
|
|
2137
|
+
function animate() {
|
|
2138
|
+
requestAnimationFrame(animate);
|
|
2139
|
+
controls.update();
|
|
2140
|
+
|
|
2141
|
+
const time = Date.now() * 0.001;
|
|
2142
|
+
|
|
2143
|
+
if (musicData.isPlaying) {
|
|
2144
|
+
analyzeMusicData();
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
const audioIntensity = musicData.volume * 0.7;
|
|
2148
|
+
const bassBoost = musicData.bass * 1.2;
|
|
2149
|
+
const midBoost = musicData.mid * 0.9;
|
|
2150
|
+
const trebleBoost = musicData.treble * 0.8;
|
|
2151
|
+
const beatPulse = musicData.beatDetected ? 1.5 : 1.0;
|
|
2152
|
+
|
|
2153
|
+
// デスクランプの光の揺らぎ
|
|
2154
|
+
if (deskLamp && deskLamp.userData.light) {
|
|
2155
|
+
deskLamp.userData.light.intensity = 1.5 + Math.sin(time * 2) * 0.3 * (1 + audioIntensity * 0.5) * beatPulse;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// フロアランプの光の揺らぎ
|
|
2159
|
+
if (floorLamp && floorLamp.userData.light) {
|
|
2160
|
+
floorLamp.userData.light.intensity = 2 + Math.sin(time * 1.5 + 1) * 0.4 * (1 + audioIntensity * 0.6) * beatPulse;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// ノートPCの画面の光
|
|
2164
|
+
if (laptop) {
|
|
2165
|
+
const screen = laptop.children[0];
|
|
2166
|
+
if (screen && screen.material && screen.material.emissiveIntensity !== undefined) {
|
|
2167
|
+
screen.material.emissiveIntensity = 0.3 + Math.sin(time * 3) * 0.1 * (1 + trebleBoost * 0.5) * beatPulse;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// サボテンの揺れ
|
|
2172
|
+
if (cactus1) {
|
|
2173
|
+
cactus1.rotation.z = Math.sin(time * 0.5) * 0.02 * (1 + bassBoost * 0.3) * beatPulse;
|
|
2174
|
+
}
|
|
2175
|
+
if (cactus2) {
|
|
2176
|
+
cactus2.rotation.z = Math.sin(time * 0.6 + 1) * 0.02 * (1 + bassBoost * 0.3) * beatPulse;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
// パーティクルの動き
|
|
2180
|
+
if (dustParticles) {
|
|
2181
|
+
dustParticles.rotation.y = time * 0.01 * (1 + audioIntensity * 0.3);
|
|
2182
|
+
const positions = dustParticles.geometry.attributes.position.array;
|
|
2183
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
2184
|
+
positions[i + 1] += Math.sin(time + i) * 0.0005 * (1 + midBoost * 0.4) * beatPulse;
|
|
2185
|
+
if (positions[i + 1] > 8) positions[i + 1] = 0;
|
|
2186
|
+
if (positions[i + 1] < 0) positions[i + 1] = 8;
|
|
2187
|
+
}
|
|
2188
|
+
dustParticles.geometry.attributes.position.needsUpdate = true;
|
|
2189
|
+
|
|
2190
|
+
if (dustParticles.material && dustParticles.material.opacity !== undefined) {
|
|
2191
|
+
dustParticles.material.opacity = (0.7 + trebleBoost * 0.3) * beatPulse;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// 星空のキラキラ
|
|
2196
|
+
if (starField && starField.material) {
|
|
2197
|
+
starField.material.opacity = 0.7 + Math.sin(time * 2) * 0.2 + audioIntensity * 0.3;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// 虹色ライトの色変化
|
|
2201
|
+
if (rainbowLight) {
|
|
2202
|
+
const hue = (time * 0.1) % 1;
|
|
2203
|
+
rainbowLight.color.setHSL(hue, 0.8, 0.7);
|
|
2204
|
+
rainbowLight.intensity = audioIntensity * 3 * beatPulse;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// フェアリーライトの点滅
|
|
2208
|
+
if (fairyLights1) {
|
|
2209
|
+
fairyLights1.children.forEach((child, index) => {
|
|
2210
|
+
if (child.userData.phase !== undefined) {
|
|
2211
|
+
if (child.material && child.material.emissiveIntensity !== undefined) {
|
|
2212
|
+
child.material.emissiveIntensity = child.userData.originalIntensity *
|
|
2213
|
+
(0.5 + Math.sin(time * 2 + child.userData.phase) * 0.5) *
|
|
2214
|
+
(1 + audioIntensity * 0.5) * beatPulse;
|
|
2215
|
+
}
|
|
2216
|
+
if (child.intensity !== undefined) {
|
|
2217
|
+
child.intensity = child.userData.originalIntensity *
|
|
2218
|
+
(0.5 + Math.sin(time * 2 + child.userData.phase) * 0.5) *
|
|
2219
|
+
(1 + audioIntensity * 0.5) * beatPulse;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
if (fairyLights2) {
|
|
2225
|
+
fairyLights2.children.forEach((child, index) => {
|
|
2226
|
+
if (child.userData.phase !== undefined) {
|
|
2227
|
+
if (child.material && child.material.emissiveIntensity !== undefined) {
|
|
2228
|
+
child.material.emissiveIntensity = child.userData.originalIntensity *
|
|
2229
|
+
(0.5 + Math.sin(time * 2.5 + child.userData.phase) * 0.5) *
|
|
2230
|
+
(1 + audioIntensity * 0.5) * beatPulse;
|
|
2231
|
+
}
|
|
2232
|
+
if (child.intensity !== undefined) {
|
|
2233
|
+
child.intensity = child.userData.originalIntensity *
|
|
2234
|
+
(0.5 + Math.sin(time * 2.5 + child.userData.phase) * 0.5) *
|
|
2235
|
+
(1 + audioIntensity * 0.5) * beatPulse;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// 猫のアニメーション
|
|
2242
|
+
if (cat) {
|
|
2243
|
+
// ビートに合わせて跳ねる
|
|
2244
|
+
cat.position.y = cat.userData.originalY + (musicData.beatDetected ? 0.05 : 0) +
|
|
2245
|
+
Math.sin(time * 2) * 0.02 * (1 + bassBoost * 0.3);
|
|
2246
|
+
|
|
2247
|
+
// しっぽを振る
|
|
2248
|
+
cat.userData.tailAngle += 0.05 * (1 + audioIntensity);
|
|
2249
|
+
const tail = cat.children.find(child => child.geometry && child.geometry.type === 'CylinderGeometry' && child.position.z < 0);
|
|
2250
|
+
if (tail) {
|
|
2251
|
+
tail.rotation.z = Math.sin(cat.userData.tailAngle) * 0.3 * (1 + bassBoost);
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// 耳が動く
|
|
2255
|
+
const ears = cat.children.filter(child => child.geometry && child.geometry.type === 'ConeGeometry');
|
|
2256
|
+
ears.forEach((ear, index) => {
|
|
2257
|
+
if (musicData.beatDetected) {
|
|
2258
|
+
ear.rotation.x = (index === 0 ? -0.3 : 0.3) + Math.sin(time * 5) * 0.1;
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// 風船の浮遊
|
|
2264
|
+
balloons.forEach((balloon) => {
|
|
2265
|
+
balloon.userData.floatPhase += 0.01 * balloon.userData.floatSpeed;
|
|
2266
|
+
balloon.position.y = balloon.userData.originalY +
|
|
2267
|
+
Math.sin(balloon.userData.floatPhase) * 0.5 * (1 + audioIntensity * 0.3) * beatPulse;
|
|
2268
|
+
balloon.rotation.z = Math.sin(time + balloon.userData.floatPhase) * 0.1;
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
// モビールの回転
|
|
2272
|
+
if (starMobile) {
|
|
2273
|
+
starMobile.rotation.y += 0.002 * (1 + audioIntensity * 0.5);
|
|
2274
|
+
starMobile.children.forEach((child, index) => {
|
|
2275
|
+
if (child.material && child.material.emissiveIntensity !== undefined) {
|
|
2276
|
+
child.material.emissiveIntensity = 0.6 + Math.sin(time * 3 + index) * 0.3 + audioIntensity * 0.5;
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// 吊り下げ植物の揺れ
|
|
2282
|
+
if (hangingPlant1) {
|
|
2283
|
+
hangingPlant1.userData.swingPhase += 0.01;
|
|
2284
|
+
hangingPlant1.rotation.z = Math.sin(hangingPlant1.userData.swingPhase) * 0.05 * (1 + bassBoost * 0.3);
|
|
2285
|
+
}
|
|
2286
|
+
if (hangingPlant2) {
|
|
2287
|
+
hangingPlant2.userData.swingPhase += 0.01;
|
|
2288
|
+
hangingPlant2.rotation.z = Math.sin(hangingPlant2.userData.swingPhase) * 0.05 * (1 + bassBoost * 0.3);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
// ポスターが音楽で光る
|
|
2292
|
+
[poster1, poster2, poster3, poster4].forEach((poster, index) => {
|
|
2293
|
+
if (poster && poster.children[1] && poster.children[1].material) {
|
|
2294
|
+
poster.children[1].material.emissiveIntensity = 0.2 + audioIntensity * 0.3 +
|
|
2295
|
+
(musicData.beatDetected ? 0.2 : 0);
|
|
2296
|
+
}
|
|
2297
|
+
});
|
|
2298
|
+
|
|
2299
|
+
// オーロラの動き
|
|
2300
|
+
if (aurora) {
|
|
2301
|
+
aurora.children.forEach((layer, index) => {
|
|
2302
|
+
const vertices = layer.geometry.attributes.position.array;
|
|
2303
|
+
for (let i = 0; i < vertices.length; i += 3) {
|
|
2304
|
+
const x = vertices[i];
|
|
2305
|
+
vertices[i + 2] = Math.sin(x * 0.5 + time * 0.5 + layer.userData.offset) * 0.3 *
|
|
2306
|
+
(1 + audioIntensity * 0.5) * beatPulse;
|
|
2307
|
+
}
|
|
2308
|
+
layer.geometry.attributes.position.needsUpdate = true;
|
|
2309
|
+
|
|
2310
|
+
// 音楽に合わせて色が変わる
|
|
2311
|
+
const hue = 0.5 + index * 0.1 + audioIntensity * 0.2 + (time * 0.05) % 0.3;
|
|
2312
|
+
layer.material.color.setHSL(hue, 0.8, 0.6);
|
|
2313
|
+
layer.material.opacity = (0.15 + index * 0.05) * (1 + audioIntensity * 0.3) * beatPulse;
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// 浮遊する球体の動き
|
|
2318
|
+
if (floatingOrbs) {
|
|
2319
|
+
floatingOrbs.forEach((orb, index) => {
|
|
2320
|
+
// 円軌道を描く
|
|
2321
|
+
const angle = time * orb.userData.speed + orb.userData.phase;
|
|
2322
|
+
orb.position.x = Math.cos(angle) * orb.userData.radius;
|
|
2323
|
+
orb.position.z = Math.sin(angle) * orb.userData.radius;
|
|
2324
|
+
|
|
2325
|
+
// 上下に浮遊
|
|
2326
|
+
orb.userData.verticalPhase += 0.02;
|
|
2327
|
+
orb.position.y = orb.userData.height + Math.sin(orb.userData.verticalPhase) * 0.5 *
|
|
2328
|
+
(1 + audioIntensity * 0.4) * beatPulse;
|
|
2329
|
+
|
|
2330
|
+
// 自転
|
|
2331
|
+
orb.rotation.x += 0.01 * (1 + bassBoost * 0.5);
|
|
2332
|
+
orb.rotation.y += 0.02 * (1 + midBoost * 0.5);
|
|
2333
|
+
|
|
2334
|
+
// 音楽に合わせて明滅
|
|
2335
|
+
orb.material.opacity = 0.6 + audioIntensity * 0.3 + (musicData.beatDetected ? 0.2 : 0);
|
|
2336
|
+
|
|
2337
|
+
// グロー効果
|
|
2338
|
+
const glow = orb.children[0];
|
|
2339
|
+
if (glow) {
|
|
2340
|
+
glow.material.opacity = 0.2 + audioIntensity * 0.2 * beatPulse;
|
|
2341
|
+
glow.scale.setScalar(1 + Math.sin(time * 3 + index) * 0.2 * (1 + audioIntensity * 0.5));
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// 魔法のポータルのアニメーション
|
|
2347
|
+
if (magicPortal) {
|
|
2348
|
+
magicPortal.children.forEach((ring, index) => {
|
|
2349
|
+
if (ring.userData.rotationSpeed) {
|
|
2350
|
+
ring.rotation.z += ring.userData.rotationSpeed * 0.01 * ring.userData.direction *
|
|
2351
|
+
(1 + audioIntensity * 0.5);
|
|
2352
|
+
|
|
2353
|
+
// 音楽に合わせて光る
|
|
2354
|
+
if (ring.material) {
|
|
2355
|
+
const baseOpacity = 0.8 - index * 0.15;
|
|
2356
|
+
ring.material.opacity = baseOpacity * (1 + audioIntensity * 0.4) * beatPulse;
|
|
2357
|
+
|
|
2358
|
+
// 色が変化
|
|
2359
|
+
const hue = 0.6 + index * 0.1 + (time * 0.1) % 0.4;
|
|
2360
|
+
ring.material.color.setHSL(hue, 1, 0.6);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
|
|
2365
|
+
// ポータル中心の脈動
|
|
2366
|
+
const center = magicPortal.children[magicPortal.children.length - 1];
|
|
2367
|
+
if (center && center.material) {
|
|
2368
|
+
center.material.opacity = 0.3 + Math.sin(time * 2) * 0.2 + audioIntensity * 0.3 * beatPulse;
|
|
2369
|
+
center.scale.setScalar(1 + Math.sin(time * 3) * 0.1 * (1 + bassBoost * 0.5));
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// ホログラフィックビジュアライザー
|
|
2374
|
+
if (holographicVisualizer && holographicVisualizer.userData.bars) {
|
|
2375
|
+
holographicVisualizer.rotation.y += 0.005 * (1 + audioIntensity * 0.5);
|
|
2376
|
+
|
|
2377
|
+
holographicVisualizer.userData.bars.forEach((bar, index) => {
|
|
2378
|
+
// 周波数データに基づいて球体のサイズを変える
|
|
2379
|
+
let freqValue = 0;
|
|
2380
|
+
if (musicData.frequencyData && musicData.frequencyData.length > 0) {
|
|
2381
|
+
const freqIndex = Math.floor((index / holographicVisualizer.userData.bars.length) * musicData.frequencyData.length);
|
|
2382
|
+
freqValue = musicData.frequencyData[freqIndex] / 255;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
const targetScale = bar.userData.baseScale * (0.8 + freqValue * 2) * (1 + audioIntensity * 0.5);
|
|
2386
|
+
const currentScale = bar.scale.x;
|
|
2387
|
+
const newScale = currentScale * 0.8 + targetScale * 0.2; // スムーズな補間
|
|
2388
|
+
bar.scale.setScalar(newScale);
|
|
2389
|
+
|
|
2390
|
+
// 色が変化
|
|
2391
|
+
const hue = (index / holographicVisualizer.userData.bars.length + time * 0.1) % 1;
|
|
2392
|
+
bar.material.color.setHSL(hue, 0.9, 0.6);
|
|
2393
|
+
|
|
2394
|
+
// 明度
|
|
2395
|
+
bar.material.opacity = 0.7 + freqValue * 0.3 * beatPulse;
|
|
2396
|
+
|
|
2397
|
+
// 球体が回転
|
|
2398
|
+
bar.rotation.x += 0.01 * (1 + freqValue * 2);
|
|
2399
|
+
bar.rotation.y += 0.01 * (1 + freqValue * 2);
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
renderer.render(scene, camera);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
animate();
|
|
2407
|
+
|
|
2408
|
+
// Handle window resize
|
|
2409
|
+
window.addEventListener('resize', () => {
|
|
2410
|
+
camera.aspect = window.innerWidth / window.innerHeight;
|
|
2411
|
+
camera.updateProjectionMatrix();
|
|
2412
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
2413
|
+
});
|
|
2414
|
+
|
|
2415
|
+
// 30秒後の自動非表示タイマー開始
|
|
2416
|
+
scheduleInfoPanelAutoHide();
|
|
2417
|
+
|
|
2418
|
+
console.log('✅ Lo-fi Room準備完了!🎧');
|
|
2419
|
+
|
|
2420
|
+
})();
|
|
2421
|
+
</script>
|
|
2422
|
+
|
|
2423
|
+
<!-- VR/AR Button inline implementation -->
|
|
2424
|
+
<script>
|
|
2425
|
+
// VRButton implementation
|
|
2426
|
+
const VRButton = {
|
|
2427
|
+
createButton: function(renderer) {
|
|
2428
|
+
const button = document.createElement('button');
|
|
2429
|
+
|
|
2430
|
+
function showEnterVR() {
|
|
2431
|
+
button.style.display = '';
|
|
2432
|
+
button.style.cursor = 'pointer';
|
|
2433
|
+
button.style.left = 'calc(50% - 50px)';
|
|
2434
|
+
button.style.width = '100px';
|
|
2435
|
+
button.textContent = 'ENTER VR';
|
|
2436
|
+
button.onmouseenter = function() { button.style.opacity = '1.0'; };
|
|
2437
|
+
button.onmouseleave = function() { button.style.opacity = '0.5'; };
|
|
2438
|
+
button.onclick = function() {
|
|
2439
|
+
const session = renderer.xr.getSession();
|
|
2440
|
+
if (session) {
|
|
2441
|
+
session.end();
|
|
2442
|
+
} else {
|
|
2443
|
+
navigator.xr.requestSession('immersive-vr', {
|
|
2444
|
+
optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking']
|
|
2445
|
+
}).then(session => {
|
|
2446
|
+
renderer.xr.setSession(session);
|
|
2447
|
+
button.textContent = 'EXIT VR';
|
|
2448
|
+
|
|
2449
|
+
session.addEventListener('end', () => {
|
|
2450
|
+
button.textContent = 'ENTER VR';
|
|
2451
|
+
});
|
|
2452
|
+
}).catch(err => {
|
|
2453
|
+
console.error('VR session failed:', err);
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function showWebXRNotFound() {
|
|
2460
|
+
button.style.display = '';
|
|
2461
|
+
button.style.cursor = 'auto';
|
|
2462
|
+
button.style.left = 'calc(50% - 75px)';
|
|
2463
|
+
button.style.width = '150px';
|
|
2464
|
+
button.textContent = 'VR NOT SUPPORTED';
|
|
2465
|
+
button.onmouseenter = null;
|
|
2466
|
+
button.onmouseleave = null;
|
|
2467
|
+
button.onclick = null;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
button.style.position = 'absolute';
|
|
2471
|
+
button.style.bottom = '20px';
|
|
2472
|
+
button.style.padding = '12px 6px';
|
|
2473
|
+
button.style.border = '1px solid #fff';
|
|
2474
|
+
button.style.borderRadius = '4px';
|
|
2475
|
+
button.style.background = 'rgba(0,0,0,0.1)';
|
|
2476
|
+
button.style.color = '#fff';
|
|
2477
|
+
button.style.font = 'normal 13px sans-serif';
|
|
2478
|
+
button.style.textAlign = 'center';
|
|
2479
|
+
button.style.opacity = '0.5';
|
|
2480
|
+
button.style.outline = 'none';
|
|
2481
|
+
button.style.zIndex = '999';
|
|
2482
|
+
|
|
2483
|
+
if ('xr' in navigator) {
|
|
2484
|
+
navigator.xr.isSessionSupported('immersive-vr').then(supported => {
|
|
2485
|
+
supported ? showEnterVR() : showWebXRNotFound();
|
|
2486
|
+
});
|
|
2487
|
+
} else {
|
|
2488
|
+
showWebXRNotFound();
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
return button;
|
|
2492
|
+
}
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2495
|
+
// ARButton implementation
|
|
2496
|
+
const ARButton = {
|
|
2497
|
+
createButton: function(renderer, options) {
|
|
2498
|
+
const button = document.createElement('button');
|
|
2499
|
+
|
|
2500
|
+
function showEnterAR() {
|
|
2501
|
+
button.style.display = '';
|
|
2502
|
+
button.style.cursor = 'pointer';
|
|
2503
|
+
button.style.left = 'calc(50% - 50px)';
|
|
2504
|
+
button.style.width = '100px';
|
|
2505
|
+
button.textContent = 'ENTER AR';
|
|
2506
|
+
button.onmouseenter = function() { button.style.opacity = '1.0'; };
|
|
2507
|
+
button.onmouseleave = function() { button.style.opacity = '0.5'; };
|
|
2508
|
+
button.onclick = function() {
|
|
2509
|
+
const session = renderer.xr.getSession();
|
|
2510
|
+
if (session) {
|
|
2511
|
+
session.end();
|
|
2512
|
+
} else {
|
|
2513
|
+
navigator.xr.requestSession('immersive-ar', {
|
|
2514
|
+
optionalFeatures: ['hit-test', 'dom-overlay', 'local-floor'],
|
|
2515
|
+
domOverlay: options?.domOverlay || { root: document.body }
|
|
2516
|
+
}).then(session => {
|
|
2517
|
+
renderer.xr.setSession(session);
|
|
2518
|
+
button.textContent = 'EXIT AR';
|
|
2519
|
+
|
|
2520
|
+
session.addEventListener('end', () => {
|
|
2521
|
+
button.textContent = 'ENTER AR';
|
|
2522
|
+
});
|
|
2523
|
+
}).catch(err => {
|
|
2524
|
+
console.error('AR session failed:', err);
|
|
2525
|
+
alert('AR not supported: ' + err.message);
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
function showWebXRNotFound() {
|
|
2532
|
+
button.style.display = '';
|
|
2533
|
+
button.style.cursor = 'auto';
|
|
2534
|
+
button.style.left = 'calc(50% - 75px)';
|
|
2535
|
+
button.style.width = '150px';
|
|
2536
|
+
button.textContent = 'AR NOT SUPPORTED';
|
|
2537
|
+
button.onmouseenter = null;
|
|
2538
|
+
button.onmouseleave = null;
|
|
2539
|
+
button.onclick = null;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
button.style.position = 'absolute';
|
|
2543
|
+
button.style.bottom = '70px';
|
|
2544
|
+
button.style.padding = '12px 6px';
|
|
2545
|
+
button.style.border = '1px solid #fff';
|
|
2546
|
+
button.style.borderRadius = '4px';
|
|
2547
|
+
button.style.background = 'rgba(0,0,0,0.1)';
|
|
2548
|
+
button.style.color = '#fff';
|
|
2549
|
+
button.style.font = 'normal 13px sans-serif';
|
|
2550
|
+
button.style.textAlign = 'center';
|
|
2551
|
+
button.style.opacity = '0.5';
|
|
2552
|
+
button.style.outline = 'none';
|
|
2553
|
+
button.style.zIndex = '999';
|
|
2554
|
+
|
|
2555
|
+
if ('xr' in navigator) {
|
|
2556
|
+
navigator.xr.isSessionSupported('immersive-ar').then(supported => {
|
|
2557
|
+
supported ? showEnterAR() : showWebXRNotFound();
|
|
2558
|
+
});
|
|
2559
|
+
} else {
|
|
2560
|
+
showWebXRNotFound();
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
return button;
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
|
|
2567
|
+
console.log('✅ VRButton/ARButton inline implementation loaded');
|
|
2568
|
+
</script>
|
|
2569
|
+
</body>
|
|
2570
|
+
</html>
|