Three.js / TresJS Development Skill
File Organization: This skill uses split structure. See references/ for advanced patterns and security examples.
- Overview
This skill provides expertise for building 3D HUD interfaces using Three.js and TresJS (Vue 3 integration). It focuses on creating performant, visually stunning holographic displays for the JARVIS AI Assistant.
Risk Level: MEDIUM - GPU resource consumption, potential ReDoS in color parsing, WebGL security considerations
Primary Use Cases:
Rendering 3D holographic HUD panels Animated status indicators and gauges Particle effects for system visualization Real-time metric displays with 3D elements 2. Core Responsibilities 2.1 Fundamental Principles TDD First: Write tests before implementation - verify 3D components render correctly Performance Aware: Optimize for 60fps with instancing, LOD, and efficient render loops Resource Management: Always dispose of geometries, materials, and textures to prevent memory leaks Vue Reactivity Integration: Use TresJS for seamless Vue 3 composition API integration Safe Color Parsing: Validate color inputs to prevent ReDoS attacks GPU Protection: Implement safeguards against GPU resource exhaustion Accessibility: Provide fallbacks for devices without WebGL support 3. Technology Stack & Versions 3.1 Recommended Versions Package Version Security Notes three ^0.160.0+ Latest stable, fixes CVE-2020-28496 ReDoS @tresjs/core ^4.0.0 Vue 3 integration @tresjs/cientos ^3.0.0 Component library postprocessing ^6.0.0 Effects library 3.2 Security-Critical Updates { "dependencies": { "three": "^0.160.0", "@tresjs/core": "^4.0.0", "@tresjs/cientos": "^3.0.0" } }
Note: Versions before 0.137.0 have XSS vulnerabilities, before 0.125.0 have ReDoS vulnerabilities.
- Implementation Patterns 4.1 Basic HUD Scene Setup
<HUDPanels />
<MetricsDisplay />
<ParticleEffects />
4.2 Secure Color Handling // utils/safeColor.ts import { Color } from 'three'
// ✅ Safe color parsing with validation export function safeParseColor(input: string): Color { // Validate format to prevent ReDoS const hexPattern = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/ const rgbPattern = /^rgb(\s\d{1,3}\s,\s\d{1,3}\s,\s\d{1,3}\s)$/
if (!hexPattern.test(input) && !rgbPattern.test(input)) { console.warn('Invalid color format, using default') return new Color(0x00ff00) // Default JARVIS green }
return new Color(input) }
// ❌ DANGEROUS - User input directly to Color // const color = new Color(userInput) // Potential ReDoS
// ✅ SECURE - Validated input const color = safeParseColor(userInput)
4.3 Memory-Safe Component
<script setup lang="ts"> import { onUnmounted, shallowRef } from 'vue' import { Mesh, BoxGeometry, MeshStandardMaterial } from 'three' // ✅ Use shallowRef for Three.js objects const meshRef = shallowRef
4.4 Performance-Optimized Instancing
<script setup lang="ts"> import { ref, onMounted } from 'vue' import { InstancedMesh, Object3D, Matrix4 } from 'three' const instanceCount = 1000 const instancedMeshRef = ref
4.5 HUD Panel with Text
<script setup lang="ts"> import { Text } from '@tresjs/cientos' const props = defineProps<{ title: string value: number }>() // ✅ Sanitize text content const safeTitle = computed(() => props.title.replace(/[<>]/g, '').slice(0, 50) ) </script>
<!-- Title text -->
<Text
:text="safeTitle"
:font-size="0.15"
color="#00ff41"
:position="[-0.8, 0.3, 0.01]"
/>
<!-- Value display -->
<Text
:text="String(props.value)"
:font-size="0.3"
color="#ffffff"
:position="[0, -0.1, 0.01]"
/>
- Implementation Workflow (TDD) 5.1 TDD Process for 3D Components
Step 1: Write Failing Test First
// tests/components/hud-panel.test.ts import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { mount, VueWrapper } from '@vue/test-utils' import { Scene, WebGLRenderer } from 'three' import HUDPanel from '~/components/hud/HUDPanel.vue'
describe('HUDPanel', () => { let wrapper: VueWrapper
beforeEach(() => { // Mock WebGL context for testing const canvas = document.createElement('canvas') const gl = canvas.getContext('webgl2') vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(gl) })
afterEach(() => { wrapper?.unmount() vi.restoreAllMocks() })
it('renders panel with correct dimensions', () => { wrapper = mount(HUDPanel, { props: { width: 2, height: 1, title: 'Status' } })
// Test fails until component is implemented
expect(wrapper.exists()).toBe(true)
})
it('disposes resources on unmount', async () => { wrapper = mount(HUDPanel, { props: { width: 2, height: 1, title: 'Status' } })
const disposeSpy = vi.fn()
wrapper.vm.meshRef.geometry.dispose = disposeSpy
wrapper.unmount()
expect(disposeSpy).toHaveBeenCalled()
}) })
Step 2: Implement Minimum to Pass
<script setup lang="ts"> import { shallowRef, onUnmounted } from 'vue' import { Mesh } from 'three' const props = defineProps<{ width: number height: number title: string }>() const meshRef = shallowRef
Step 3: Refactor Following Patterns
// After tests pass, add performance optimizations // - Use instancing for multiple panels // - Add LOD for distant panels // - Implement texture atlases for text
Step 4: Run Full Verification
Run all tests
npm test
Run with coverage
npm test -- --coverage
Type check
npm run typecheck
Performance benchmark
npm run test:perf
5.2 Testing 3D Animations import { describe, it, expect, vi } from 'vitest' import { useRenderLoop } from '@tresjs/core'
describe('Animation Loop', () => { it('maintains 60fps during animation', async () => { const frameTimes: number[] = [] let lastTime = performance.now()
const { onLoop } = useRenderLoop()
onLoop(() => {
const now = performance.now()
frameTimes.push(now - lastTime)
lastTime = now
})
// Simulate 60 frames
await new Promise(resolve => setTimeout(resolve, 1000))
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length
expect(avgFrameTime).toBeLessThan(16.67) // 60fps = 16.67ms per frame
})
it('cleans up animation loop on unmount', () => { const cleanup = vi.fn() const { pause } = useRenderLoop()
// Component unmounts
pause()
expect(cleanup).not.toThrow()
}) })
5.3 Testing Resource Disposal describe('Resource Management', () => { it('disposes all GPU resources', () => { const geometry = new BoxGeometry(1, 1, 1) const material = new MeshStandardMaterial({ color: 0x00ff41 }) const mesh = new Mesh(geometry, material)
const geoDispose = vi.spyOn(geometry, 'dispose')
const matDispose = vi.spyOn(material, 'dispose')
// Cleanup function
mesh.geometry.dispose()
mesh.material.dispose()
expect(geoDispose).toHaveBeenCalled()
expect(matDispose).toHaveBeenCalled()
})
it('handles material arrays correctly', () => { const materials = [ new MeshBasicMaterial(), new MeshStandardMaterial() ] const mesh = new Mesh(new BoxGeometry(), materials)
const spies = materials.map(m => vi.spyOn(m, 'dispose'))
materials.forEach(m => m.dispose())
spies.forEach(spy => expect(spy).toHaveBeenCalled())
}) })
- Performance Patterns 6.1 Geometry Instancing // Good: Use InstancedMesh for repeated objects import { InstancedMesh, Matrix4, Object3D } from 'three'
const COUNT = 1000 const mesh = new InstancedMesh(geometry, material, COUNT) const dummy = new Object3D()
for (let i = 0; i < COUNT; i++) { dummy.position.set(Math.random() * 10, Math.random() * 10, Math.random() * 10) dummy.updateMatrix() mesh.setMatrixAt(i, dummy.matrix) } mesh.instanceMatrix.needsUpdate = true
// Bad: Creating individual meshes for (let i = 0; i < COUNT; i++) { const mesh = new Mesh(geometry.clone(), material.clone()) // Memory waste! scene.add(mesh) }
6.2 Texture Atlases // Good: Single texture atlas for multiple sprites const atlas = new TextureLoader().load('/textures/hud-atlas.png') const materials = { panel: new SpriteMaterial({ map: atlas }), icon: new SpriteMaterial({ map: atlas }) }
// Set UV offsets for different sprites materials.panel.map.offset.set(0, 0.5) materials.panel.map.repeat.set(0.5, 0.5)
// Bad: Loading separate textures const panelTex = new TextureLoader().load('/textures/panel.png') const iconTex = new TextureLoader().load('/textures/icon.png') // Multiple draw calls, more GPU memory
6.3 Level of Detail (LOD) // Good: Use LOD for complex objects import { LOD } from 'three'
const lod = new LOD()
// High detail - close up const highDetail = new Mesh( new SphereGeometry(1, 32, 32), material ) lod.addLevel(highDetail, 0)
// Medium detail - mid range const medDetail = new Mesh( new SphereGeometry(1, 16, 16), material ) lod.addLevel(medDetail, 10)
// Low detail - far away const lowDetail = new Mesh( new SphereGeometry(1, 8, 8), material ) lod.addLevel(lowDetail, 20)
scene.add(lod)
// Bad: Always rendering high detail const sphere = new Mesh(new SphereGeometry(1, 64, 64), material)
6.4 Frustum Culling // Good: Enable frustum culling (default, but verify) mesh.frustumCulled = true
// For custom bounds optimization mesh.geometry.computeBoundingSphere() mesh.geometry.computeBoundingBox()
// Manual visibility check for complex scenes const frustum = new Frustum() const matrix = new Matrix4().multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) frustum.setFromProjectionMatrix(matrix)
objects.forEach(obj => { obj.visible = frustum.intersectsObject(obj) })
// Bad: Disabling culling or rendering everything mesh.frustumCulled = false // Renders even when off-screen
6.5 Object Pooling
// Good: Pool and reuse objects
class ParticlePool {
private pool: Mesh[] = []
private active: Set
constructor(private geometry: BufferGeometry, private material: Material) { // Pre-allocate pool for (let i = 0; i < 100; i++) { const mesh = new Mesh(geometry, material) mesh.visible = false this.pool.push(mesh) } }
acquire(): Mesh | null { const mesh = this.pool.find(m => !this.active.has(m)) if (mesh) { mesh.visible = true this.active.add(mesh) return mesh } return null }
release(mesh: Mesh): void { mesh.visible = false this.active.delete(mesh) } }
// Bad: Creating/destroying objects each frame function spawnParticle() { const mesh = new Mesh(geometry, material) // GC pressure! scene.add(mesh) setTimeout(() => { scene.remove(mesh) mesh.geometry.dispose() }, 1000) }
6.6 RAF Optimization // Good: Efficient render loop let lastTime = 0 const targetFPS = 60 const frameInterval = 1000 / targetFPS
function animate(currentTime: number) { requestAnimationFrame(animate)
const delta = currentTime - lastTime
// Skip frame if too soon (for battery saving) if (delta < frameInterval) return
lastTime = currentTime - (delta % frameInterval)
// Update only what changed if (needsUpdate) { updateScene() renderer.render(scene, camera) } }
// Bad: Rendering every frame unconditionally function animate() { requestAnimationFrame(animate)
// Always updates everything updateAllObjects() renderer.render(scene, camera) // Even if nothing changed }
6.7 Shader Optimization
// Good: Simple, optimized shaders
const material = new ShaderMaterial({
vertexShader: varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
},
fragmentShader: varying vec2 vUv;
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
},
uniforms: {
color: { value: new Color(0x00ff41) }
}
})
// Bad: Complex calculations in fragment shader // Avoid: loops, conditionals, texture lookups when possible
- Security Standards 7.1 Known Vulnerabilities CVE Severity Description Mitigation CVE-2020-28496 HIGH ReDoS in color parsing Update to 0.125.0+, validate colors CVE-2022-0177 MEDIUM XSS in docs Update to 0.137.0+ 7.2 OWASP Top 10 Coverage OWASP Category Risk Mitigation A05 Injection MEDIUM Validate all color/text inputs A06 Vulnerable Components HIGH Keep Three.js updated 7.3 GPU Resource Protection // composables/useResourceLimit.ts export function useResourceLimit() { const MAX_TRIANGLES = 1_000_000 const MAX_DRAW_CALLS = 100
let triangleCount = 0
function checkGeometry(geometry: BufferGeometry): boolean { const triangles = geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3
if (triangleCount + triangles > MAX_TRIANGLES) {
console.error('Triangle limit exceeded')
return false
}
triangleCount += triangles
return true
}
return { checkGeometry } }
- Testing & Quality 6.1 Component Testing import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils'
describe('HUD Panel', () => { it('sanitizes malicious title input', () => { const wrapper = mount(HUDPanel, { props: { title: '<script>alert("xss")</script>Status', value: 75 } })
expect(wrapper.vm.safeTitle).not.toContain('<script>')
}) })
6.2 Performance Testing describe('Instanced Mesh', () => { it('handles 1000 instances without frame drop', async () => { const scene = new Scene() // Setup instanced mesh...
const startTime = performance.now()
renderer.render(scene, camera)
const renderTime = performance.now() - startTime
expect(renderTime).toBeLessThan(16.67) // 60fps target
}) })
- Common Mistakes & Anti-Patterns 8.1 Critical Security Anti-Patterns Never: Parse User Colors Directly // ❌ DANGEROUS - ReDoS vulnerability const color = new Color(userInput)
// ✅ SECURE - Validated input const color = safeParseColor(userInput)
Never: Skip Resource Disposal // ❌ MEMORY LEAK const mesh = new Mesh(geometry, material) scene.remove(mesh) // Geometry and material still in GPU memory!
// ✅ PROPER CLEANUP scene.remove(mesh) mesh.geometry.dispose() mesh.material.dispose()
8.2 Performance Anti-Patterns Avoid: Creating Objects in Render Loop // ❌ BAD - Creates garbage every frame function animate() { mesh.position.add(new Vector3(0, 0.01, 0)) // New object every frame! }
// ✅ GOOD - Reuse objects const velocity = new Vector3(0, 0.01, 0) function animate() { mesh.position.add(velocity) }
- Pre-Deployment Checklist Security Verification Three.js version >= 0.137.0 (XSS fix) All color inputs validated before parsing No user input directly to new Color() Resource limits enforced Performance Verification All geometries/materials disposed on unmount Instancing used for repeated objects No object creation in render loop LOD implemented for complex scenes
- Summary
Three.js/TresJS provides 3D rendering for the JARVIS HUD:
Security: Validate all inputs, especially colors to prevent ReDoS Memory: Always dispose resources on component unmount Performance: Use instancing, avoid allocations in render loop Integration: TresJS provides seamless Vue 3 reactivity
Remember: WebGL has direct GPU access - always validate inputs and manage resources carefully.
References:
references/advanced-patterns.md - Complex 3D patterns references/security-examples.md - WebGL security practices