|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ARController { |
|
|
constructor(scene, camera, renderer) { |
|
|
this.scene = scene; |
|
|
this.camera = camera; |
|
|
this.renderer = renderer; |
|
|
this.isInitialized = false; |
|
|
this.isSessionActive = false; |
|
|
this.session = null; |
|
|
this.referenceSpace = null; |
|
|
this.planes = new Map(); |
|
|
this.anchors = new Map(); |
|
|
this.trackedImages = new Map(); |
|
|
this.currentAnchor = null; |
|
|
|
|
|
|
|
|
this.config = { |
|
|
sessionType: 'immersive-ar', |
|
|
requiredFeatures: ['local'], |
|
|
optionalFeatures: ['hit-test', 'dom-overlay', 'plane-detection', 'light-estimation'], |
|
|
maxAnchors: 20, |
|
|
planeDetection: true, |
|
|
domOverlay: { |
|
|
root: document.body |
|
|
}, |
|
|
trackedImages: [] |
|
|
}; |
|
|
|
|
|
|
|
|
this.mockPlanes = this.createMockPlanes(); |
|
|
|
|
|
|
|
|
this.systemState = { |
|
|
lightIntensity: 0.8, |
|
|
planeCount: 0, |
|
|
anchorCount: 0, |
|
|
isTracking: false |
|
|
}; |
|
|
|
|
|
console.log('📱 تم تهيئة AR Controller'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async initialize() { |
|
|
try { |
|
|
console.log('🔧 بدء تهيئة AR...'); |
|
|
|
|
|
|
|
|
if (!navigator.xr) { |
|
|
throw new Error('WebXR غير مدعوم في هذا المتصفح'); |
|
|
} |
|
|
|
|
|
|
|
|
const isARAvailable = await navigator.xr.isSessionSupported(this.config.sessionType); |
|
|
if (!isARAvailable) { |
|
|
throw new Error('AR غير متاح على هذا الجهاز'); |
|
|
} |
|
|
|
|
|
|
|
|
if (this.config.planeDetection) { |
|
|
await this.initializePlaneDetection(); |
|
|
} |
|
|
|
|
|
|
|
|
await this.initializeLightEstimation(); |
|
|
|
|
|
|
|
|
if (this.config.trackedImages.length > 0) { |
|
|
await this.initializeImageTracking(); |
|
|
} |
|
|
|
|
|
this.isInitialized = true; |
|
|
console.log('✅ تم تهيئة AR بنجاح'); |
|
|
|
|
|
return true; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('❌ خطأ في تهيئة AR:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async initializePlaneDetection() { |
|
|
console.log('🟦 تهيئة كشف الأسطح...'); |
|
|
|
|
|
|
|
|
this.planeDetection = { |
|
|
planes: new Map(), |
|
|
onPlaneDetected: (plane) => { |
|
|
this.handlePlaneDetected(plane); |
|
|
}, |
|
|
onPlaneUpdated: (plane) => { |
|
|
this.handlePlaneUpdated(plane); |
|
|
}, |
|
|
onPlaneRemoved: (plane) => { |
|
|
this.handlePlaneRemoved(plane); |
|
|
} |
|
|
}; |
|
|
|
|
|
console.log('✅ تم تهيئة كشف الأسطح'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async initializeLightEstimation() { |
|
|
console.log('💡 تهيئة تقدير الإضاءة...'); |
|
|
|
|
|
this.lightEstimation = { |
|
|
isAvailable: true, |
|
|
currentLight: { |
|
|
primaryLightDirection: { x: 0.5, y: -1, z: 0.3 }, |
|
|
primaryLightIntensity: 0.8, |
|
|
sphericalHarmonics: { |
|
|
coefficients: Array(9).fill(0).map(() => Math.random() * 0.5) |
|
|
} |
|
|
}, |
|
|
update: (estimation) => { |
|
|
this.handleLightEstimationUpdate(estimation); |
|
|
} |
|
|
}; |
|
|
|
|
|
console.log('✅ تم تهيئة تقدير الإضاءة'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async initializeImageTracking() { |
|
|
console.log('🖼️ تهيئة تتبع الصور...'); |
|
|
|
|
|
|
|
|
for (const imageData of this.config.trackedImages) { |
|
|
try { |
|
|
const image = await this.loadImage(imageData.src); |
|
|
this.trackedImages.set(imageData.id, { |
|
|
image: image, |
|
|
width: imageData.width, |
|
|
tracked: false, |
|
|
pose: null |
|
|
}); |
|
|
} catch (error) { |
|
|
console.warn(`تعذر تحميل صورة التتبع ${imageData.id}:`, error); |
|
|
} |
|
|
} |
|
|
|
|
|
console.log('✅ تم تهيئة تتبع الصور'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadImage(src) { |
|
|
return new Promise((resolve, reject) => { |
|
|
const img = new Image(); |
|
|
img.crossOrigin = 'anonymous'; |
|
|
img.onload = () => resolve(img); |
|
|
img.onerror = reject; |
|
|
img.src = src; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async startSession() { |
|
|
if (!this.isInitialized) { |
|
|
throw new Error('AR غير مهيأ بعد'); |
|
|
} |
|
|
|
|
|
try { |
|
|
console.log('🚀 بدء جلسة AR...'); |
|
|
|
|
|
|
|
|
this.session = await navigator.xr.requestSession(this.config.sessionType, { |
|
|
requiredFeatures: this.config.requiredFeatures, |
|
|
optionalFeatures: this.config.optionalFeatures, |
|
|
domOverlay: this.config.domOverlay |
|
|
}); |
|
|
|
|
|
|
|
|
this.bindSessionEvents(); |
|
|
|
|
|
|
|
|
this.renderer.xr.enabled = true; |
|
|
this.renderer.xr.setReferenceSpaceType('local'); |
|
|
await this.renderer.xr.setSession(this.session); |
|
|
|
|
|
this.isSessionActive = true; |
|
|
this.startSessionLoop(); |
|
|
|
|
|
console.log('✅ تم بدء جلسة AR بنجاح'); |
|
|
this.emit('session_start'); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('❌ خطأ في بدء جلسة AR:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async endSession() { |
|
|
if (!this.session || !this.isSessionActive) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
console.log('🛑 إنهاء جلسة AR...'); |
|
|
|
|
|
this.isSessionActive = false; |
|
|
|
|
|
if (this.session) { |
|
|
await this.session.end(); |
|
|
this.session = null; |
|
|
} |
|
|
|
|
|
|
|
|
this.renderer.xr.enabled = false; |
|
|
|
|
|
|
|
|
this.cleanupAnchors(); |
|
|
this.cleanupPlanes(); |
|
|
|
|
|
console.log('✅ تم إنهاء جلسة AR'); |
|
|
this.emit('session_end'); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('❌ خطأ في إنهاء جلسة AR:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bindSessionEvents() { |
|
|
this.session.addEventListener('end', () => { |
|
|
this.isSessionActive = false; |
|
|
this.emit('session_end'); |
|
|
}); |
|
|
|
|
|
this.session.addEventListener('inputsourceschange', (event) => { |
|
|
this.handleInputSourcesChange(event); |
|
|
}); |
|
|
|
|
|
this.session.addEventListener('select', (event) => { |
|
|
this.handleSelect(event); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleInputSourcesChange(event) { |
|
|
const { added, removed } = event; |
|
|
|
|
|
added.forEach(inputSource => { |
|
|
console.log('➕ إضافة مصدر إدخال AR:', inputSource.handedness); |
|
|
}); |
|
|
|
|
|
removed.forEach(inputSource => { |
|
|
console.log('➖ إزالة مصدر إدخال AR:', inputSource.handedness); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleSelect(event) { |
|
|
console.log('👆 حدث اختيار في AR'); |
|
|
|
|
|
|
|
|
this.performHitTest(event); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async performHitTest(event) { |
|
|
try { |
|
|
|
|
|
const hitTestResults = await this.simulateHitTest(event); |
|
|
|
|
|
if (hitTestResults.length > 0) { |
|
|
const hit = hitTestResults[0]; |
|
|
console.log('🎯 hit test نجح:', hit); |
|
|
|
|
|
|
|
|
await this.createAnchor(hit.position, hit.rotation); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.warn('خطأ في hit test:', error); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
simulateHitTest(event) { |
|
|
return new Promise((resolve) => { |
|
|
|
|
|
const results = []; |
|
|
|
|
|
|
|
|
const availablePlanes = Array.from(this.planes.values()); |
|
|
|
|
|
if (availablePlanes.length > 0) { |
|
|
const plane = availablePlanes[0]; |
|
|
|
|
|
|
|
|
const result = { |
|
|
position: { |
|
|
x: plane.center.x + (Math.random() - 0.5) * plane.width, |
|
|
y: plane.center.y, |
|
|
z: plane.center.z + (Math.random() - 0.5) * plane.depth |
|
|
}, |
|
|
rotation: { x: 0, y: 0, z: 0, w: 1 }, |
|
|
plane: plane |
|
|
}; |
|
|
|
|
|
results.push(result); |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(() => resolve(results), 100); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async createAnchor(position, rotation) { |
|
|
try { |
|
|
const anchorId = `anchor_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; |
|
|
|
|
|
|
|
|
const anchorMesh = this.createAnchorMesh(); |
|
|
anchorMesh.position.set(position.x, position.y, position.z); |
|
|
|
|
|
if (rotation) { |
|
|
anchorMesh.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w); |
|
|
} |
|
|
|
|
|
this.scene.add(anchorMesh); |
|
|
|
|
|
|
|
|
const anchor = { |
|
|
id: anchorId, |
|
|
position: position, |
|
|
rotation: rotation, |
|
|
mesh: anchorMesh, |
|
|
objects: [], |
|
|
createdAt: new Date() |
|
|
}; |
|
|
|
|
|
this.anchors.set(anchorId, anchor); |
|
|
this.systemState.anchorCount = this.anchors.size; |
|
|
|
|
|
console.log('📌 تم إنشاء مرساة:', anchorId); |
|
|
this.emit('anchor_created', anchor); |
|
|
|
|
|
return anchor; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في إنشاء المرساة:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createAnchorMesh() { |
|
|
|
|
|
const torusGeometry = new THREE.TorusGeometry(0.1, 0.02, 8, 16); |
|
|
const torusMaterial = new THREE.MeshBasicMaterial({ |
|
|
color: 0x22d3ee, |
|
|
transparent: true, |
|
|
opacity: 0.8 |
|
|
}); |
|
|
|
|
|
const torus = new THREE.Mesh(torusGeometry, torusMaterial); |
|
|
torus.rotation.x = Math.PI / 2; |
|
|
|
|
|
|
|
|
const centerGeometry = new THREE.SphereGeometry(0.02, 8, 8); |
|
|
const centerMaterial = new THREE.MeshBasicMaterial({ |
|
|
color: 0x22d3ee, |
|
|
emissive: 0x22d3ee, |
|
|
emissiveIntensity: 0.3 |
|
|
}); |
|
|
|
|
|
const center = new THREE.Mesh(centerGeometry, centerMaterial); |
|
|
torus.add(center); |
|
|
|
|
|
|
|
|
const animate = () => { |
|
|
torus.scale.setScalar(1 + Math.sin(Date.now() * 0.005) * 0.1); |
|
|
requestAnimationFrame(animate); |
|
|
}; |
|
|
animate(); |
|
|
|
|
|
return torus; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
placeObjectOnAnchor(anchorId, objectType, data = {}) { |
|
|
const anchor = this.anchors.get(anchorId); |
|
|
if (!anchor) { |
|
|
throw new Error(`المرساة ${anchorId} غير موجودة`); |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const object = this.createObject3D(objectType, data); |
|
|
|
|
|
|
|
|
object.position.set(0, 0, 0); |
|
|
anchor.mesh.add(object); |
|
|
|
|
|
|
|
|
anchor.objects.push({ |
|
|
id: `object_${Date.now()}`, |
|
|
type: objectType, |
|
|
mesh: object, |
|
|
data: data, |
|
|
createdAt: new Date() |
|
|
}); |
|
|
|
|
|
console.log('📦 تم وضع كائن على المرساة:', objectType); |
|
|
this.emit('object_placed', { anchor, object }); |
|
|
|
|
|
return object; |
|
|
|
|
|
} catch (error) { |
|
|
console.error('خطأ في وضع الكائن:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createObject3D(type, data) { |
|
|
let geometry, material, mesh; |
|
|
|
|
|
switch (type) { |
|
|
case 'threat': |
|
|
geometry = this.createThreatGeometry(data.threatType || 'XSS'); |
|
|
material = new THREE.MeshBasicMaterial({ |
|
|
color: this.getThreatColor(data.severity), |
|
|
transparent: true, |
|
|
opacity: 0.8 |
|
|
}); |
|
|
mesh = new THREE.Mesh(geometry, material); |
|
|
break; |
|
|
|
|
|
case 'network_node': |
|
|
geometry = new THREE.SphereGeometry(0.05, 16, 16); |
|
|
material = new THREE.MeshBasicMaterial({ |
|
|
color: 0x22d3ee, |
|
|
transparent: true, |
|
|
opacity: 0.9 |
|
|
}); |
|
|
mesh = new THREE.Mesh(geometry, material); |
|
|
break; |
|
|
|
|
|
case 'info_panel': |
|
|
geometry = new THREE.PlaneGeometry(0.3, 0.2); |
|
|
material = new THREE.MeshBasicMaterial({ |
|
|
color: 0x000000, |
|
|
transparent: true, |
|
|
opacity: 0.8, |
|
|
side: THREE.DoubleSide |
|
|
}); |
|
|
mesh = new THREE.Mesh(geometry, material); |
|
|
|
|
|
|
|
|
const canvas = document.createElement('canvas'); |
|
|
canvas.width = 256; |
|
|
canvas.height = 128; |
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; |
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.fillStyle = '#22d3ee'; |
|
|
ctx.font = '16px Arial'; |
|
|
ctx.textAlign = 'center'; |
|
|
ctx.fillText(data.title || 'معلومات', canvas.width / 2, 40); |
|
|
|
|
|
ctx.fillStyle = '#f0f2f5'; |
|
|
ctx.font = '12px Arial'; |
|
|
ctx.fillText(data.description || 'وصف المعلومات', canvas.width / 2, 80); |
|
|
|
|
|
const texture = new THREE.CanvasTexture(canvas); |
|
|
material.map = texture; |
|
|
break; |
|
|
|
|
|
default: |
|
|
geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1); |
|
|
material = new THREE.MeshBasicMaterial({ color: 0x22d3ee }); |
|
|
mesh = new THREE.Mesh(geometry, material); |
|
|
} |
|
|
|
|
|
|
|
|
mesh.userData = { |
|
|
type: type, |
|
|
data: data, |
|
|
createdAt: new Date() |
|
|
}; |
|
|
|
|
|
return mesh; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createThreatGeometry(threatType) { |
|
|
switch (threatType) { |
|
|
case 'XSS': |
|
|
return new THREE.SphereGeometry(0.05, 16, 16); |
|
|
case 'SQL Injection': |
|
|
return new THREE.BoxGeometry(0.08, 0.08, 0.08); |
|
|
case 'Malware': |
|
|
return new THREE.TetrahedronGeometry(0.07); |
|
|
case 'DDoS': |
|
|
return new THREE.OctahedronGeometry(0.06); |
|
|
case 'Phishing': |
|
|
return new THREE.ConeGeometry(0.05, 0.1, 8); |
|
|
case 'Ransomware': |
|
|
return new THREE.TorusGeometry(0.05, 0.02, 8, 16); |
|
|
default: |
|
|
return new THREE.IcosahedronGeometry(0.05); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getThreatColor(severity) { |
|
|
switch (severity) { |
|
|
case 'منخفض': |
|
|
return 0x34d399; |
|
|
case 'متوسط': |
|
|
return 0xfbbf24; |
|
|
case 'عالي': |
|
|
return 0xf97316; |
|
|
case 'حرج': |
|
|
return 0xf87171; |
|
|
default: |
|
|
return 0x22d3ee; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handlePlaneDetected(plane) { |
|
|
console.log('🟦 تم اكتشاف سطح جديد'); |
|
|
|
|
|
|
|
|
const planeMesh = this.createPlaneMesh(plane); |
|
|
|
|
|
|
|
|
this.planes.set(plane.id, { |
|
|
id: plane.id, |
|
|
center: plane.center, |
|
|
width: plane.width, |
|
|
height: plane.height, |
|
|
orientation: plane.orientation, |
|
|
mesh: planeMesh, |
|
|
createdAt: new Date() |
|
|
}); |
|
|
|
|
|
this.systemState.planeCount = this.planes.size; |
|
|
|
|
|
|
|
|
this.scene.add(planeMesh); |
|
|
|
|
|
this.emit('plane_detected', plane); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handlePlaneUpdated(plane) { |
|
|
const trackedPlane = this.planes.get(plane.id); |
|
|
if (trackedPlane) { |
|
|
|
|
|
trackedPlane.center = plane.center; |
|
|
trackedPlane.width = plane.width; |
|
|
trackedPlane.height = plane.height; |
|
|
|
|
|
|
|
|
trackedPlane.mesh.position.set(plane.center.x, plane.center.y, plane.center.z); |
|
|
trackedPlane.mesh.rotation.setFromQuaternion(plane.orientation); |
|
|
|
|
|
|
|
|
trackedPlane.mesh.scale.set(plane.width, 1, plane.height); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handlePlaneRemoved(plane) { |
|
|
const trackedPlane = this.planes.get(plane.id); |
|
|
if (trackedPlane) { |
|
|
this.scene.remove(trackedPlane.mesh); |
|
|
this.planes.delete(plane.id); |
|
|
this.systemState.planeCount = this.planes.size; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createPlaneMesh(plane) { |
|
|
const geometry = new THREE.PlaneGeometry(1, 1); |
|
|
const material = new THREE.MeshBasicMaterial({ |
|
|
color: 0x22d3ee, |
|
|
transparent: true, |
|
|
opacity: 0.1, |
|
|
side: THREE.DoubleSide |
|
|
}); |
|
|
|
|
|
const mesh = new THREE.Mesh(geometry, material); |
|
|
|
|
|
|
|
|
mesh.position.set(plane.center.x, plane.center.y, plane.center.z); |
|
|
mesh.quaternion.set(plane.orientation.x, plane.orientation.y, plane.orientation.z, plane.orientation.w); |
|
|
mesh.scale.set(plane.width, 1, plane.height); |
|
|
|
|
|
|
|
|
const gridHelper = new THREE.GridHelper(plane.width, 10, 0x22d3ee, 0x22d3ee); |
|
|
gridHelper.position.y = 0.001; |
|
|
mesh.add(gridHelper); |
|
|
|
|
|
return mesh; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
createMockPlanes() { |
|
|
return [ |
|
|
{ |
|
|
id: 'mock_plane_1', |
|
|
center: { x: 0, y: 0, z: -1 }, |
|
|
width: 1, |
|
|
height: 0.8, |
|
|
orientation: { x: 0, y: 0, z: 0, w: 1 } |
|
|
} |
|
|
]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleLightEstimationUpdate(estimation) { |
|
|
if (estimation) { |
|
|
this.systemState.lightIntensity = estimation.primaryLightIntensity || 0.8; |
|
|
|
|
|
|
|
|
this.updateSceneLighting(estimation); |
|
|
|
|
|
console.log('💡 تم تحديث تقدير الإضاءة:', this.systemState.lightIntensity); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateSceneLighting(lightEstimation) { |
|
|
|
|
|
const directionalLight = this.scene.children.find(child => |
|
|
child.isDirectionalLight && child.userData.isMainLight |
|
|
); |
|
|
|
|
|
if (directionalLight) { |
|
|
|
|
|
if (lightEstimation.primaryLightDirection) { |
|
|
const dir = lightEstimation.primaryLightDirection; |
|
|
directionalLight.position.set(dir.x * 10, dir.y * 10, dir.z * 10); |
|
|
} |
|
|
|
|
|
|
|
|
if (lightEstimation.primaryLightIntensity) { |
|
|
directionalLight.intensity = lightEstimation.primaryLightIntensity; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
startSessionLoop() { |
|
|
const animate = () => { |
|
|
if (!this.isSessionActive) return; |
|
|
|
|
|
|
|
|
this.updatePlanes(); |
|
|
|
|
|
|
|
|
this.updateAnchors(); |
|
|
|
|
|
|
|
|
this.updateImageTracking(); |
|
|
|
|
|
|
|
|
this.updateLightEstimation(); |
|
|
|
|
|
|
|
|
this.session.requestAnimationFrame(animate); |
|
|
}; |
|
|
|
|
|
this.session.requestAnimationFrame(animate); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updatePlanes() { |
|
|
|
|
|
if (this.mockPlanes.length > 0) { |
|
|
this.mockPlanes.forEach(mockPlane => { |
|
|
|
|
|
let plane = this.planes.get(mockPlane.id); |
|
|
if (!plane) { |
|
|
|
|
|
this.handlePlaneDetected(mockPlane); |
|
|
} else { |
|
|
|
|
|
this.handlePlaneUpdated(mockPlane); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateAnchors() { |
|
|
this.anchors.forEach(anchor => { |
|
|
|
|
|
if (anchor.mesh) { |
|
|
|
|
|
anchor.mesh.rotation.y += 0.01; |
|
|
|
|
|
|
|
|
const distanceFromCamera = this.camera.position.distanceTo(anchor.mesh.position); |
|
|
anchor.mesh.visible = distanceFromCamera < 5; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateImageTracking() { |
|
|
this.trackedImages.forEach((imageData, id) => { |
|
|
|
|
|
if (Math.random() > 0.95) { |
|
|
if (!imageData.tracked) { |
|
|
imageData.tracked = true; |
|
|
imageData.pose = this.generateRandomPose(); |
|
|
console.log('🖼️ تم تتبع صورة:', id); |
|
|
this.emit('image_tracked', { id, pose: imageData.pose }); |
|
|
} |
|
|
} else { |
|
|
imageData.tracked = false; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
generateRandomPose() { |
|
|
return { |
|
|
position: { |
|
|
x: (Math.random() - 0.5) * 2, |
|
|
y: Math.random() * 2, |
|
|
z: (Math.random() - 0.5) * 2 |
|
|
}, |
|
|
rotation: { |
|
|
x: 0, |
|
|
y: (Math.random() - 0.5) * Math.PI * 2, |
|
|
z: 0, |
|
|
w: 1 |
|
|
} |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateLightEstimation() { |
|
|
|
|
|
const time = Date.now() * 0.001; |
|
|
this.systemState.lightIntensity = 0.7 + Math.sin(time * 0.5) * 0.3; |
|
|
|
|
|
if (this.lightEstimation) { |
|
|
this.lightEstimation.currentLight.primaryLightIntensity = this.systemState.lightIntensity; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleanupAnchors() { |
|
|
this.anchors.forEach(anchor => { |
|
|
if (anchor.mesh) { |
|
|
this.scene.remove(anchor.mesh); |
|
|
} |
|
|
}); |
|
|
this.anchors.clear(); |
|
|
this.systemState.anchorCount = 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleanupPlanes() { |
|
|
this.planes.forEach(plane => { |
|
|
if (plane.mesh) { |
|
|
this.scene.remove(plane.mesh); |
|
|
} |
|
|
}); |
|
|
this.planes.clear(); |
|
|
this.systemState.planeCount = 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getSystemState() { |
|
|
return { |
|
|
...this.systemState, |
|
|
isSessionActive: this.isSessionActive, |
|
|
planes: Array.from(this.planes.values()).map(plane => ({ |
|
|
id: plane.id, |
|
|
center: plane.center, |
|
|
width: plane.width, |
|
|
height: plane.height |
|
|
})), |
|
|
anchors: Array.from(this.anchors.values()).map(anchor => ({ |
|
|
id: anchor.id, |
|
|
position: anchor.position, |
|
|
objectCount: anchor.objects.length |
|
|
})) |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
displayThreatInAR(threat, position) { |
|
|
if (!this.isSessionActive) { |
|
|
throw new Error('جلسة AR غير نشطة'); |
|
|
} |
|
|
|
|
|
|
|
|
this.createAnchor(position, { x: 0, y: 0, z: 0, w: 1 }) |
|
|
.then(anchor => { |
|
|
|
|
|
this.placeObjectOnAnchor(anchor.id, 'threat', { |
|
|
threatType: threat.type, |
|
|
severity: threat.severity, |
|
|
description: threat.description |
|
|
}); |
|
|
|
|
|
console.log('⚠️ تم عرض تهديد في AR:', threat.type); |
|
|
}) |
|
|
.catch(error => { |
|
|
console.error('خطأ في عرض التهديد في AR:', error); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
displayNetworkMapInAR(networkData, centerPosition) { |
|
|
if (!this.isSessionActive) { |
|
|
throw new Error('جلسة AR غير نشطة'); |
|
|
} |
|
|
|
|
|
|
|
|
this.createAnchor(centerPosition, { x: 0, y: 0, z: 0, w: 1 }) |
|
|
.then(anchor => { |
|
|
|
|
|
networkData.nodes.forEach((node, index) => { |
|
|
const nodePosition = { |
|
|
x: (index % 5) * 0.3 - 0.6, |
|
|
y: 0, |
|
|
z: Math.floor(index / 5) * 0.3 - 0.6 |
|
|
}; |
|
|
|
|
|
this.placeObjectOnAnchor(anchor.id, 'network_node', { |
|
|
zone: node.zone, |
|
|
status: node.status, |
|
|
connections: node.connections |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.placeObjectOnAnchor(anchor.id, 'info_panel', { |
|
|
title: 'خريطة الشبكة', |
|
|
description: `${networkData.nodes.length} عقدة نشطة` |
|
|
}); |
|
|
|
|
|
console.log('🌐 تم عرض خريطة شبكة في AR'); |
|
|
}) |
|
|
.catch(error => { |
|
|
console.error('خطأ في عرض خريطة الشبكة في AR:', error); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
emit(eventName, data) { |
|
|
if (this.eventListeners[eventName]) { |
|
|
this.eventListeners[eventName].forEach(callback => { |
|
|
try { |
|
|
callback(data); |
|
|
} catch (error) { |
|
|
console.error('خطأ في مستمع الحدث:', error); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
on(eventName, callback) { |
|
|
if (!this.eventListeners[eventName]) { |
|
|
this.eventListeners[eventName] = []; |
|
|
} |
|
|
this.eventListeners[eventName].push(callback); |
|
|
} |
|
|
|
|
|
off(eventName, callback) { |
|
|
if (this.eventListeners[eventName]) { |
|
|
this.eventListeners[eventName] = this.eventListeners[eventName].filter(cb => cb !== callback); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
destroy() { |
|
|
console.log('🗑️ تدمير AR Controller...'); |
|
|
|
|
|
|
|
|
if (this.isSessionActive) { |
|
|
this.endSession(); |
|
|
} |
|
|
|
|
|
|
|
|
this.cleanupAnchors(); |
|
|
this.cleanupPlanes(); |
|
|
|
|
|
this.trackedImages.clear(); |
|
|
this.planes.clear(); |
|
|
|
|
|
console.log('✅ تم تدمير AR Controller'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (typeof module !== 'undefined' && module.exports) { |
|
|
module.exports = ARController; |
|
|
} |