@c4h/chuci 0.2.5 → 0.2.6

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.
@@ -1,385 +1,385 @@
1
- import { CcViewerBase } from './cc-viewer-base'
2
-
3
- export class CcViewerGaussian extends CcViewerBase {
4
- private splatUrl = ''
5
- private debugMode = false
6
- private cameraPosition = '3,3,3'
7
- private _cameraTarget = '0,0,0' // TODO: Implement camera target functionality
8
-
9
- // gsplat.js library types are not available, using any for external library objects
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- private scene?: any
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- private camera?: any
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- private renderer?: any
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- private controls?: any
18
- private animationId?: number
19
- private canvas?: HTMLCanvasElement
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- private swiper?: any
22
-
23
- static get observedAttributes() {
24
- return ['show', 'debug-mode', 'camera-position', 'camera-target']
25
- }
26
-
27
- attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
28
- if (name === 'show') {
29
- this.isShow = newValue === 'true'
30
- } else if (name === 'debug-mode') {
31
- this.debugMode = newValue === 'true'
32
- } else if (name === 'camera-position') {
33
- this.cameraPosition = newValue || '3,3,3'
34
- } else if (name === 'camera-target') {
35
- this._cameraTarget = newValue || '0,0,0'
36
- }
37
- super.attributeChangedCallback(name, oldValue, newValue)
38
- }
39
-
40
- // Implementation of abstract methods from base class
41
- protected async doOpen(url: string): Promise<void> {
42
- this.splatUrl = url
43
- await this.initializeViewer()
44
- }
45
-
46
- protected doClose(): void {
47
- this.cleanup()
48
- }
49
-
50
- protected getViewerContent(): string {
51
- // This won't be used since we override render, but required by abstract class
52
- return ''
53
- }
54
-
55
- // Override to use custom rendering due to special canvas handling
56
- protected shouldUseCustomRender(): boolean {
57
- return true
58
- }
59
-
60
- // Custom render implementation
61
- protected customRender(): void {
62
- const styles = this.css`
63
- :host {
64
- --cc-viewer-z-index-each: 1000;
65
- }
66
-
67
- .backdrop {
68
- justify-content: center;
69
- align-items: center;
70
- position: fixed;
71
- left: 0;
72
- right: 0;
73
- top: 0;
74
- bottom: 0;
75
- width: 100%;
76
- height: 100%;
77
- background-color: rgba(0, 0, 0, 0.9);
78
- z-index: var(--cc-viewer-z-index-each, 1000);
79
- }
80
-
81
- .viewer {
82
- position: absolute;
83
- width: 90%;
84
- height: 85%;
85
- inset: 0px;
86
- margin: auto;
87
- align-self: center;
88
- background-color: #000;
89
- }
90
-
91
- .gaussian-container {
92
- width: 100%;
93
- height: 100%;
94
- position: relative;
95
- background: #000;
96
- }
97
-
98
- .gaussian-container canvas {
99
- width: 100% !important;
100
- height: 100% !important;
101
- display: block;
102
- }
103
-
104
- .loading {
105
- position: absolute;
106
- top: 50%;
107
- left: 50%;
108
- transform: translate(-50%, -50%);
109
- color: #fff;
110
- font-size: 1.2rem;
111
- }
112
-
113
- .error {
114
- position: absolute;
115
- top: 50%;
116
- left: 50%;
117
- transform: translate(-50%, -50%);
118
- color: #e74c3c;
119
- text-align: center;
120
- padding: 20px;
121
- }
122
-
123
- canvas {
124
- display: block;
125
- width: 100%;
126
- height: 100%;
127
- touch-action: none;
128
- }
129
-
130
- .debug-info {
131
- position: absolute;
132
- top: 10px;
133
- left: 10px;
134
- background: rgba(0, 0, 0, 0.85);
135
- color: #00ff00;
136
- padding: 12px;
137
- font-family: 'Courier New', monospace;
138
- font-size: 11px;
139
- line-height: 1.4;
140
- border-radius: 6px;
141
- border: 1px solid rgba(0, 255, 0, 0.3);
142
- pointer-events: none;
143
- white-space: pre-line;
144
- min-width: 200px;
145
- z-index: calc(var(--cc-viewer-z-index-each, 1000) + 3);
146
- }
147
-
148
- ${this.getNavigationStyles()}
149
- `
150
-
151
- const gaussianContent = `
152
- <canvas style="display: none;"></canvas>
153
- ${this.isLoading ? '<div class="loading">Loading...</div>' : ''}
154
- ${!this.isLoading && this.debugMode ? `
155
- <div class="debug-info">
156
- 📍 Camera Position:
157
- ${this.getCameraDebugInfo()}
158
-
159
- 🎯 Camera Target:
160
- ${this.getTargetDebugInfo()}
161
-
162
- 🎮 Controls:
163
- • Rotate: Left-drag
164
- • Zoom: Scroll wheel
165
- • Pan: Right-drag or Shift+Left-drag
166
-
167
- 📊 Status: ${this.scene ? 'Splat loaded' : 'Loading...'}
168
- </div>
169
- ` : ''}
170
- `
171
-
172
- const html = `
173
- ${styles}
174
- <div class="backdrop" style="${this.isShow ? 'visibility: visible' : 'visibility: hidden'}">
175
- <div class="viewer">
176
- <div class="gaussian-container">
177
- ${gaussianContent}
178
- </div>
179
- </div>
180
- ${this.getNavigationButtons()}
181
- </div>
182
- `
183
-
184
- this.updateShadowRoot(html)
185
-
186
- // Add navigation listeners after render
187
- setTimeout(() => {
188
- this.addNavigationListeners()
189
- }, 0)
190
- }
191
-
192
- private cleanup() {
193
- if (this.animationId) {
194
- cancelAnimationFrame(this.animationId)
195
- this.animationId = undefined
196
- }
197
-
198
- if (this.renderer && typeof this.renderer.dispose === 'function') {
199
- this.renderer.dispose()
200
- }
201
-
202
- // Remove all gaussian canvases from document.body
203
- const existingCanvases = document.querySelectorAll('canvas[id^="gaussian-canvas-"]')
204
- existingCanvases.forEach(canvas => {
205
- if (canvas.parentNode === document.body) {
206
- document.body.removeChild(canvas)
207
- }
208
- })
209
-
210
- this.scene = undefined
211
- this.camera = undefined
212
- this.renderer = undefined
213
- this.controls = undefined
214
- this.canvas = undefined
215
- }
216
-
217
- private getCameraDebugInfo(): string {
218
- if (!this.camera || !this.camera.position) return 'Position: unavailable'
219
- const pos = this.camera.position
220
- try {
221
- return `X: ${pos.x.toFixed(3)}, Y: ${pos.y.toFixed(3)}, Z: ${pos.z.toFixed(3)}`
222
- } catch (_error) {
223
- return `Position: ${JSON.stringify(pos)}`
224
- }
225
- }
226
-
227
- private getTargetDebugInfo(): string {
228
- if (!this.controls) return 'Target: controls unavailable'
229
- // gsplat.js OrbitControls might not have a target property
230
- // Return available control info instead
231
- try {
232
- return `Controls active (no target property in gsplat.js)`
233
- } catch (_error) {
234
- return `Target: ${JSON.stringify(this.controls)}`
235
- }
236
- }
237
-
238
- private updateDebugInfo() {
239
- // Update debug display
240
- const debugEl = this.query('.debug-info')
241
- if (debugEl) {
242
- debugEl.innerHTML = `
243
- 📍 Camera Position:
244
- ${this.getCameraDebugInfo()}
245
-
246
- 🎯 Camera Target:
247
- ${this.getTargetDebugInfo()}
248
-
249
- 🎮 Controls:
250
- • Rotate: Left-drag
251
- • Zoom: Scroll wheel
252
- • Pan: Right-drag or Shift+Left-drag
253
-
254
- 📊 Status: ${this.scene ? 'Splat loaded' : 'Loading...'}
255
- `
256
- }
257
- }
258
-
259
- private async initializeViewer() {
260
- // Create a unique ID for this instance
261
- const canvasId = `gaussian-canvas-${Date.now()}`
262
-
263
- // Get the viewer container dimensions
264
- const viewerEl = this.query('.viewer') as HTMLElement
265
- if (!viewerEl) return
266
-
267
- const rect = viewerEl.getBoundingClientRect()
268
-
269
- // Check if canvas already exists in normal DOM
270
- let normalCanvas = document.getElementById(canvasId) as HTMLCanvasElement
271
- if (!normalCanvas) {
272
- normalCanvas = document.createElement('canvas')
273
- normalCanvas.id = canvasId
274
- normalCanvas.style.position = 'fixed'
275
- normalCanvas.style.top = `${rect.top}px`
276
- normalCanvas.style.left = `${rect.left}px`
277
- normalCanvas.style.width = `${rect.width}px`
278
- normalCanvas.style.height = `${rect.height}px`
279
- // Get z-index from host style
280
- const hostStyle = window.getComputedStyle(this)
281
- const zIndexVar = hostStyle.getPropertyValue('--cc-viewer-z-index-each').trim()
282
- const baseZIndex = zIndexVar ? parseInt(zIndexVar, 10) : 1000
283
- normalCanvas.style.zIndex = `${baseZIndex + 1}` // Above backdrop but below buttons
284
- normalCanvas.style.pointerEvents = 'auto' // Keep mouse events for 3D controls
285
- normalCanvas.style.display = 'block'
286
- normalCanvas.style.background = 'transparent'
287
- document.body.appendChild(normalCanvas)
288
- }
289
-
290
- // Update canvas position in case viewer moved
291
- normalCanvas.style.top = `${rect.top}px`
292
- normalCanvas.style.left = `${rect.left}px`
293
- normalCanvas.style.width = `${rect.width}px`
294
- normalCanvas.style.height = `${rect.height}px`
295
-
296
- this.canvas = normalCanvas
297
-
298
- try {
299
- const container = this.query('.gaussian-container') as HTMLDivElement
300
- if (!container) return
301
-
302
- // Import gsplat using the proven API approach
303
- const SPLAT = await import('gsplat')
304
-
305
- // Setup GSplat components exactly like the React example
306
- this.scene = new SPLAT.Scene()
307
- this.camera = new SPLAT.Camera()
308
- this.renderer = new SPLAT.WebGLRenderer(this.canvas)
309
- this.controls = new SPLAT.OrbitControls(this.camera, this.canvas)
310
-
311
- // Load the splat file
312
- await SPLAT.Loader.LoadAsync(this.splatUrl, this.scene)
313
-
314
- // Start render loop
315
- let frameCount = 0
316
- const animate = () => {
317
- // Check if renderer still exists before continuing
318
- if (!this.renderer || !this.scene || !this.camera) {
319
- return
320
- }
321
-
322
- this.animationId = requestAnimationFrame(animate)
323
-
324
- if (this.controls) {
325
- this.controls.update()
326
- }
327
-
328
- try {
329
- this.renderer.render(this.scene, this.camera)
330
- } catch (_e) {
331
- }
332
-
333
- // Log first frame only
334
- if (frameCount === 0 && this.canvas) {
335
- frameCount++
336
- }
337
-
338
- if (this.debugMode) {
339
- this.updateDebugInfo()
340
- }
341
- }
342
- animate()
343
-
344
- // Setup resize handler
345
- const resizeObserver = new ResizeObserver(() => {
346
- this.handleResize()
347
- })
348
- resizeObserver.observe(container)
349
-
350
- } catch (error) {
351
- throw error
352
- }
353
- }
354
-
355
- private handleResize() {
356
- const container = this.query('.gaussian-container') as HTMLDivElement
357
- if (!container || !this.renderer || !this.camera) return
358
-
359
- const width = container.clientWidth
360
- const height = container.clientHeight
361
-
362
- // Update renderer size
363
- if (typeof this.renderer.setSize === 'function') {
364
- this.renderer.setSize(width, height)
365
- }
366
-
367
- // Update camera aspect ratio
368
- if (typeof this.camera.aspect !== 'undefined') {
369
- this.camera.aspect = width / height
370
- if (typeof this.camera.updateProjectionMatrix === 'function') {
371
- this.camera.updateProjectionMatrix()
372
- }
373
- }
374
- }
375
- }
376
-
377
- if (!customElements.get('cc-viewer-gaussian')) {
378
- customElements.define('cc-viewer-gaussian', CcViewerGaussian)
379
- }
380
-
381
- declare global {
382
- interface HTMLElementTagNameMap {
383
- 'cc-viewer-gaussian': CcViewerGaussian
384
- }
1
+ import { CcViewerBase } from './cc-viewer-base'
2
+
3
+ export class CcViewerGaussian extends CcViewerBase {
4
+ private splatUrl = ''
5
+ private debugMode = false
6
+ private cameraPosition = '3,3,3'
7
+ private _cameraTarget = '0,0,0' // TODO: Implement camera target functionality
8
+
9
+ // gsplat.js library types are not available, using any for external library objects
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ private scene?: any
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ private camera?: any
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ private renderer?: any
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ private controls?: any
18
+ private animationId?: number
19
+ private canvas?: HTMLCanvasElement
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ private swiper?: any
22
+
23
+ static get observedAttributes() {
24
+ return ['show', 'debug-mode', 'camera-position', 'camera-target']
25
+ }
26
+
27
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
28
+ if (name === 'show') {
29
+ this.isShow = newValue === 'true'
30
+ } else if (name === 'debug-mode') {
31
+ this.debugMode = newValue === 'true'
32
+ } else if (name === 'camera-position') {
33
+ this.cameraPosition = newValue || '3,3,3'
34
+ } else if (name === 'camera-target') {
35
+ this._cameraTarget = newValue || '0,0,0'
36
+ }
37
+ super.attributeChangedCallback(name, oldValue, newValue)
38
+ }
39
+
40
+ // Implementation of abstract methods from base class
41
+ protected async doOpen(url: string): Promise<void> {
42
+ this.splatUrl = url
43
+ await this.initializeViewer()
44
+ }
45
+
46
+ protected doClose(): void {
47
+ this.cleanup()
48
+ }
49
+
50
+ protected getViewerContent(): string {
51
+ // This won't be used since we override render, but required by abstract class
52
+ return ''
53
+ }
54
+
55
+ // Override to use custom rendering due to special canvas handling
56
+ protected shouldUseCustomRender(): boolean {
57
+ return true
58
+ }
59
+
60
+ // Custom render implementation
61
+ protected customRender(): void {
62
+ const styles = this.css`
63
+ :host {
64
+ --cc-viewer-z-index-each: 1000;
65
+ }
66
+
67
+ .backdrop {
68
+ justify-content: center;
69
+ align-items: center;
70
+ position: fixed;
71
+ left: 0;
72
+ right: 0;
73
+ top: 0;
74
+ bottom: 0;
75
+ width: 100%;
76
+ height: 100%;
77
+ background-color: rgba(0, 0, 0, 0.9);
78
+ z-index: var(--cc-viewer-z-index-each, 1000);
79
+ }
80
+
81
+ .viewer {
82
+ position: absolute;
83
+ width: 90%;
84
+ height: 85%;
85
+ inset: 0px;
86
+ margin: auto;
87
+ align-self: center;
88
+ background-color: #000;
89
+ }
90
+
91
+ .gaussian-container {
92
+ width: 100%;
93
+ height: 100%;
94
+ position: relative;
95
+ background: #000;
96
+ }
97
+
98
+ .gaussian-container canvas {
99
+ width: 100% !important;
100
+ height: 100% !important;
101
+ display: block;
102
+ }
103
+
104
+ .loading {
105
+ position: absolute;
106
+ top: 50%;
107
+ left: 50%;
108
+ transform: translate(-50%, -50%);
109
+ color: #fff;
110
+ font-size: 1.2rem;
111
+ }
112
+
113
+ .error {
114
+ position: absolute;
115
+ top: 50%;
116
+ left: 50%;
117
+ transform: translate(-50%, -50%);
118
+ color: #e74c3c;
119
+ text-align: center;
120
+ padding: 20px;
121
+ }
122
+
123
+ canvas {
124
+ display: block;
125
+ width: 100%;
126
+ height: 100%;
127
+ touch-action: none;
128
+ }
129
+
130
+ .debug-info {
131
+ position: absolute;
132
+ top: 10px;
133
+ left: 10px;
134
+ background: rgba(0, 0, 0, 0.85);
135
+ color: #00ff00;
136
+ padding: 12px;
137
+ font-family: 'Courier New', monospace;
138
+ font-size: 11px;
139
+ line-height: 1.4;
140
+ border-radius: 6px;
141
+ border: 1px solid rgba(0, 255, 0, 0.3);
142
+ pointer-events: none;
143
+ white-space: pre-line;
144
+ min-width: 200px;
145
+ z-index: calc(var(--cc-viewer-z-index-each, 1000) + 3);
146
+ }
147
+
148
+ ${this.getNavigationStyles()}
149
+ `
150
+
151
+ const gaussianContent = `
152
+ <canvas style="display: none;"></canvas>
153
+ ${this.isLoading ? '<div class="loading">Loading...</div>' : ''}
154
+ ${!this.isLoading && this.debugMode ? `
155
+ <div class="debug-info">
156
+ 📍 Camera Position:
157
+ ${this.getCameraDebugInfo()}
158
+
159
+ 🎯 Camera Target:
160
+ ${this.getTargetDebugInfo()}
161
+
162
+ 🎮 Controls:
163
+ • Rotate: Left-drag
164
+ • Zoom: Scroll wheel
165
+ • Pan: Right-drag or Shift+Left-drag
166
+
167
+ 📊 Status: ${this.scene ? 'Splat loaded' : 'Loading...'}
168
+ </div>
169
+ ` : ''}
170
+ `
171
+
172
+ const html = `
173
+ ${styles}
174
+ <div class="backdrop" style="${this.isShow ? 'visibility: visible' : 'visibility: hidden'}">
175
+ <div class="viewer">
176
+ <div class="gaussian-container">
177
+ ${gaussianContent}
178
+ </div>
179
+ </div>
180
+ ${this.getNavigationButtons()}
181
+ </div>
182
+ `
183
+
184
+ this.updateShadowRoot(html)
185
+
186
+ // Add navigation listeners after render
187
+ setTimeout(() => {
188
+ this.addNavigationListeners()
189
+ }, 0)
190
+ }
191
+
192
+ private cleanup() {
193
+ if (this.animationId) {
194
+ cancelAnimationFrame(this.animationId)
195
+ this.animationId = undefined
196
+ }
197
+
198
+ if (this.renderer && typeof this.renderer.dispose === 'function') {
199
+ this.renderer.dispose()
200
+ }
201
+
202
+ // Remove all gaussian canvases from document.body
203
+ const existingCanvases = document.querySelectorAll('canvas[id^="gaussian-canvas-"]')
204
+ existingCanvases.forEach(canvas => {
205
+ if (canvas.parentNode === document.body) {
206
+ document.body.removeChild(canvas)
207
+ }
208
+ })
209
+
210
+ this.scene = undefined
211
+ this.camera = undefined
212
+ this.renderer = undefined
213
+ this.controls = undefined
214
+ this.canvas = undefined
215
+ }
216
+
217
+ private getCameraDebugInfo(): string {
218
+ if (!this.camera || !this.camera.position) return 'Position: unavailable'
219
+ const pos = this.camera.position
220
+ try {
221
+ return `X: ${pos.x.toFixed(3)}, Y: ${pos.y.toFixed(3)}, Z: ${pos.z.toFixed(3)}`
222
+ } catch (_error) {
223
+ return `Position: ${JSON.stringify(pos)}`
224
+ }
225
+ }
226
+
227
+ private getTargetDebugInfo(): string {
228
+ if (!this.controls) return 'Target: controls unavailable'
229
+ // gsplat.js OrbitControls might not have a target property
230
+ // Return available control info instead
231
+ try {
232
+ return `Controls active (no target property in gsplat.js)`
233
+ } catch (_error) {
234
+ return `Target: ${JSON.stringify(this.controls)}`
235
+ }
236
+ }
237
+
238
+ private updateDebugInfo() {
239
+ // Update debug display
240
+ const debugEl = this.query('.debug-info')
241
+ if (debugEl) {
242
+ debugEl.innerHTML = `
243
+ 📍 Camera Position:
244
+ ${this.getCameraDebugInfo()}
245
+
246
+ 🎯 Camera Target:
247
+ ${this.getTargetDebugInfo()}
248
+
249
+ 🎮 Controls:
250
+ • Rotate: Left-drag
251
+ • Zoom: Scroll wheel
252
+ • Pan: Right-drag or Shift+Left-drag
253
+
254
+ 📊 Status: ${this.scene ? 'Splat loaded' : 'Loading...'}
255
+ `
256
+ }
257
+ }
258
+
259
+ private async initializeViewer() {
260
+ // Create a unique ID for this instance
261
+ const canvasId = `gaussian-canvas-${Date.now()}`
262
+
263
+ // Get the viewer container dimensions
264
+ const viewerEl = this.query('.viewer') as HTMLElement
265
+ if (!viewerEl) return
266
+
267
+ const rect = viewerEl.getBoundingClientRect()
268
+
269
+ // Check if canvas already exists in normal DOM
270
+ let normalCanvas = document.getElementById(canvasId) as HTMLCanvasElement
271
+ if (!normalCanvas) {
272
+ normalCanvas = document.createElement('canvas')
273
+ normalCanvas.id = canvasId
274
+ normalCanvas.style.position = 'fixed'
275
+ normalCanvas.style.top = `${rect.top}px`
276
+ normalCanvas.style.left = `${rect.left}px`
277
+ normalCanvas.style.width = `${rect.width}px`
278
+ normalCanvas.style.height = `${rect.height}px`
279
+ // Get z-index from host style
280
+ const hostStyle = window.getComputedStyle(this)
281
+ const zIndexVar = hostStyle.getPropertyValue('--cc-viewer-z-index-each').trim()
282
+ const baseZIndex = zIndexVar ? parseInt(zIndexVar, 10) : 1000
283
+ normalCanvas.style.zIndex = `${baseZIndex + 1}` // Above backdrop but below buttons
284
+ normalCanvas.style.pointerEvents = 'auto' // Keep mouse events for 3D controls
285
+ normalCanvas.style.display = 'block'
286
+ normalCanvas.style.background = 'transparent'
287
+ document.body.appendChild(normalCanvas)
288
+ }
289
+
290
+ // Update canvas position in case viewer moved
291
+ normalCanvas.style.top = `${rect.top}px`
292
+ normalCanvas.style.left = `${rect.left}px`
293
+ normalCanvas.style.width = `${rect.width}px`
294
+ normalCanvas.style.height = `${rect.height}px`
295
+
296
+ this.canvas = normalCanvas
297
+
298
+ try {
299
+ const container = this.query('.gaussian-container') as HTMLDivElement
300
+ if (!container) return
301
+
302
+ // Import gsplat using the proven API approach
303
+ const SPLAT = await import('gsplat')
304
+
305
+ // Setup GSplat components exactly like the React example
306
+ this.scene = new SPLAT.Scene()
307
+ this.camera = new SPLAT.Camera()
308
+ this.renderer = new SPLAT.WebGLRenderer(this.canvas)
309
+ this.controls = new SPLAT.OrbitControls(this.camera, this.canvas)
310
+
311
+ // Load the splat file
312
+ await SPLAT.Loader.LoadAsync(this.splatUrl, this.scene)
313
+
314
+ // Start render loop
315
+ let frameCount = 0
316
+ const animate = () => {
317
+ // Check if renderer still exists before continuing
318
+ if (!this.renderer || !this.scene || !this.camera) {
319
+ return
320
+ }
321
+
322
+ this.animationId = requestAnimationFrame(animate)
323
+
324
+ if (this.controls) {
325
+ this.controls.update()
326
+ }
327
+
328
+ try {
329
+ this.renderer.render(this.scene, this.camera)
330
+ } catch (_e) {
331
+ }
332
+
333
+ // Log first frame only
334
+ if (frameCount === 0 && this.canvas) {
335
+ frameCount++
336
+ }
337
+
338
+ if (this.debugMode) {
339
+ this.updateDebugInfo()
340
+ }
341
+ }
342
+ animate()
343
+
344
+ // Setup resize handler
345
+ const resizeObserver = new ResizeObserver(() => {
346
+ this.handleResize()
347
+ })
348
+ resizeObserver.observe(container)
349
+
350
+ } catch (error) {
351
+ throw error
352
+ }
353
+ }
354
+
355
+ private handleResize() {
356
+ const container = this.query('.gaussian-container') as HTMLDivElement
357
+ if (!container || !this.renderer || !this.camera) return
358
+
359
+ const width = container.clientWidth
360
+ const height = container.clientHeight
361
+
362
+ // Update renderer size
363
+ if (typeof this.renderer.setSize === 'function') {
364
+ this.renderer.setSize(width, height)
365
+ }
366
+
367
+ // Update camera aspect ratio
368
+ if (typeof this.camera.aspect !== 'undefined') {
369
+ this.camera.aspect = width / height
370
+ if (typeof this.camera.updateProjectionMatrix === 'function') {
371
+ this.camera.updateProjectionMatrix()
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ if (!customElements.get('cc-viewer-gaussian')) {
378
+ customElements.define('cc-viewer-gaussian', CcViewerGaussian)
379
+ }
380
+
381
+ declare global {
382
+ interface HTMLElementTagNameMap {
383
+ 'cc-viewer-gaussian': CcViewerGaussian
384
+ }
385
385
  }