Files
gwada-sirius/sirius-wave-visualization.html
T

635 lines
20 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vague Planétaire de Sirius - Lever Héliaque 2025</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: radial-gradient(circle at center, #0a192f 0%, #020c1b 100%);
font-family: 'Arial', sans-serif;
color: #ccd6f6;
}
/* Contrôles */
.controls {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 10;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
color: #0a192f;
border: none;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(255, 215, 0, 0.5);
}
.btn.secondary {
background: linear-gradient(135deg, #64ffda 0%, #64b5f6 100%);
box-shadow: 0 4px 15px rgba(100, 255, 218, 0.3);
}
/* Tooltip amélioré */
#tooltip {
position: absolute;
padding: 12px 16px;
background: rgba(10, 25, 47, 0.95);
color: #ccd6f6;
border-radius: 8px;
pointer-events: none;
display: none;
z-index: 100;
font-size: 0.9rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 215, 0, 0.3);
backdrop-filter: blur(10px);
}
#tooltip strong {
color: #ffd700;
font-size: 1.1rem;
display: block;
margin-bottom: 4px;
}
#tooltip .date {
color: #64ffda;
}
/* Info Panel */
.info-panel {
position: fixed;
bottom: 1rem;
left: 1rem;
background: rgba(10, 25, 47, 0.9);
padding: 1.5rem;
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 215, 0, 0.2);
max-width: 300px;
}
.info-panel h2 {
color: #ffd700;
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
.info-panel p {
font-size: 0.9rem;
line-height: 1.5;
color: #8892b0;
}
/* Loading */
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 1000;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 3px solid rgba(255, 215, 0, 0.3);
border-top-color: #ffd700;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
margin-top: 1rem;
color: #ffd700;
}
/* Légende */
.legend {
position: fixed;
top: 1rem;
left: 1rem;
background: rgba(10, 25, 47, 0.9);
padding: 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 215, 0, 0.2);
backdrop-filter: blur(10px);
}
.legend h3 {
color: #ffd700;
font-size: 1rem;
margin-bottom: 0.5rem;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.3rem 0;
font-size: 0.85rem;
}
.legend-icon {
width: 20px;
height: 20px;
border-radius: 50%;
}
/* Mobile responsive */
@media (max-width: 768px) {
.info-panel {
max-width: 200px;
padding: 1rem;
}
.legend {
display: none;
}
.controls {
top: 0.5rem;
right: 0.5rem;
}
.btn {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<div class="loading-text">Chargement de la carte stellaire...</div>
</div>
<!-- Controls -->
<div class="controls">
<button class="btn" id="modeBtn">Mode Manuel</button>
<button class="btn secondary" id="waveBtn">Afficher Vague</button>
<button class="btn secondary" id="resetBtn">Réinitialiser</button>
</div>
<!-- Tooltip -->
<div id="tooltip"></div>
<!-- Legend -->
<div class="legend">
<h3>Lever Héliaque de Sirius</h3>
<div class="legend-item">
<div class="legend-icon" style="background: #ffd700;"></div>
<span>Sites d'observation</span>
</div>
<div class="legend-item">
<div class="legend-icon" style="background: #64ffda;"></div>
<span>Vague de Sirius</span>
</div>
</div>
<!-- Info Panel -->
<div class="info-panel">
<h2>Wep Ronpet 2025</h2>
<p>Le lever héliaque de Sirius marque le Nouvel An égyptien. Cette visualisation montre la progression de ce phénomène à travers le monde.</p>
</div>
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Configuration
const config = {
globeRadius: 1,
markerSize: 0.08,
waveSpeed: 0.001,
autoRotateSpeed: 0.0005,
cameraDistance: 3.2,
colors: {
marker: 0xffd700,
wave: 0x64ffda,
glow: 0xffd700,
atmosphere: 0x64b5f6
}
};
// Initialize Three.js
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Scene setup
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x020c1b, 8, 20);
// Camera
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = config.cameraDistance;
// Lights
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xffffff, 1);
sunLight.position.set(5, 3, 5);
sunLight.castShadow = true;
scene.add(sunLight);
const backLight = new THREE.DirectionalLight(0x64b5f6, 0.3);
backLight.position.set(-5, -3, -5);
scene.add(backLight);
// Stars background
const starsGeometry = new THREE.BufferGeometry();
const starsCount = 5000;
const starsPositions = new Float32Array(starsCount * 3);
for(let i = 0; i < starsCount * 3; i += 3) {
const radius = 50 + Math.random() * 450;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(Math.random() * 2 - 1);
starsPositions[i] = radius * Math.sin(phi) * Math.cos(theta);
starsPositions[i + 1] = radius * Math.sin(phi) * Math.sin(theta);
starsPositions[i + 2] = radius * Math.cos(phi);
}
starsGeometry.setAttribute('position', new THREE.BufferAttribute(starsPositions, 3));
const starsMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.5,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
const stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
// Globe group
const globeGroup = new THREE.Group();
scene.add(globeGroup);
// Texture loader with error handling
const loader = new THREE.TextureLoader();
const textureUrls = {
earth: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthmap1k.jpg',
earthBump: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthbump1k.jpg',
earthSpec: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthspec1k.jpg'
};
// Sites data with accurate coordinates
const sites = [
{name: 'La Réunion', lat: -21.1151, lon: 55.5364, date: '27 Juin', time: '04:50', order: 1},
{name: 'Kinshasa', lat: -4.4419, lon: 15.2663, date: '16 Juillet', time: '05:20', order: 2},
{name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, date: '22 Juillet', time: '05:10', order: 3},
{name: 'Martinique', lat: 14.6415, lon: -61.0242, date: '23 Juillet', time: '05:08', order: 4},
{name: 'Dakar', lat: 14.7167, lon: -17.4677, date: '23 Juillet', time: '05:15', order: 5},
{name: 'Abidjan', lat: 5.3600, lon: -4.0083, date: '28 Juillet', time: '05:25', order: 6},
{name: 'Lomé', lat: 6.1375, lon: 1.2123, date: '28 Juillet', time: '05:22', order: 7},
{name: 'Accra', lat: 5.6037, lon: -0.1870, date: '28 Juillet', time: '05:23', order: 8},
{name: 'Cotonou', lat: 6.3703, lon: 2.3912, date: '28 Juillet', time: '05:24', order: 9},
{name: 'Lagos', lat: 6.5244, lon: 3.3792, date: '28 Juillet', time: '05:26', order: 10},
{name: 'Yaoundé', lat: 3.8480, lon: 11.5021, date: '29 Juillet', time: '05:28', order: 11},
{name: 'Libreville', lat: 0.4162, lon: 9.4673, date: '29 Juillet', time: '05:30', order: 12},
{name: 'Brazzaville', lat: -4.2634, lon: 15.2429, date: '29 Juillet', time: '05:32', order: 13},
{name: 'Kampala', lat: 0.3476, lon: 32.5825, date: '29 Juillet', time: '05:35', order: 14},
{name: 'Nairobi', lat: -1.2921, lon: 36.8219, date: '30 Juillet', time: '05:37', order: 15},
{name: 'Addis-Abeba', lat: 9.0320, lon: 38.7466, date: '26 Juillet', time: '05:18', order: 16},
{name: 'Le Caire', lat: 30.0444, lon: 31.2357, date: '3 Août', time: '05:00', order: 17},
{name: 'Paris', lat: 48.8566, lon: 2.3522, date: '12 Août', time: '05:45', order: 18}
];
// Load textures and create globe
Promise.all([
new Promise((resolve, reject) => {
loader.load(textureUrls.earth, resolve, undefined, reject);
}),
new Promise((resolve, reject) => {
loader.load(textureUrls.earthBump, resolve, undefined, reject);
}),
new Promise((resolve, reject) => {
loader.load(textureUrls.earthSpec, resolve, undefined, reject);
})
]).then(([earthMap, earthBump, earthSpec]) => {
// Hide loading
document.getElementById('loading').style.display = 'none';
// Create globe
const globeGeometry = new THREE.SphereGeometry(config.globeRadius, 64, 64);
const globeMaterial = new THREE.MeshPhongMaterial({
map: earthMap,
bumpMap: earthBump,
bumpScale: 0.05,
specularMap: earthSpec,
specular: new THREE.Color(0x444444),
shininess: 20
});
const globe = new THREE.Mesh(globeGeometry, globeMaterial);
globe.castShadow = true;
globe.receiveShadow = true;
globeGroup.add(globe);
// Atmosphere
const atmosphereGeometry = new THREE.SphereGeometry(config.globeRadius * 1.1, 32, 32);
const atmosphereMaterial = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec3 vNormal;
void main() {
float intensity = pow(0.7 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);
gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity;
}
`,
blending: THREE.AdditiveBlending,
side: THREE.BackSide
});
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
globeGroup.add(atmosphere);
// Wave system
const waveGroup = new THREE.Group();
globeGroup.add(waveGroup);
// Create wave rings
const waveRings = [];
for(let i = 0; i < 3; i++) {
const radius = 1.01 + (i * 0.02); // Légèrement au-dessus du globe
const waveGeometry = new THREE.TorusGeometry(radius, 0.05, 16, 100);
waveGeometry.rotateX(Math.PI / 2); // Rotation pour que le torus soit horizontal
const waveMaterial = new THREE.MeshBasicMaterial({
color: config.colors.wave,
transparent: true,
opacity: 0.4 - (i * 0.1),
side: THREE.DoubleSide
});
const wave = new THREE.Mesh(waveGeometry, waveMaterial);
waveRings.push(wave);
waveGroup.add(wave);
}
// Markers
const markersGroup = new THREE.Group();
globeGroup.add(markersGroup);
// Create custom marker geometry (simplified ankh shape)
const markerShape = new THREE.Shape();
markerShape.moveTo(0, -0.5);
markerShape.lineTo(0, 0.2);
markerShape.moveTo(-0.3, 0);
markerShape.lineTo(0.3, 0);
markerShape.moveTo(0, 0.2);
markerShape.absarc(0, 0.3, 0.2, Math.PI, 0, true);
const markerGeometry = new THREE.ShapeGeometry(markerShape);
// Add markers for each site
sites.forEach(site => {
// Conversion identique à l'exemple
const latRad = site.lat * (Math.PI / 180);
const lonRad = -site.lon * (Math.PI / 180);
const x = Math.cos(latRad) * Math.cos(lonRad);
const y = Math.sin(latRad);
const z = Math.cos(latRad) * Math.sin(lonRad);
// Marker group
const markerGroup = new THREE.Group();
// Simple sphere marker comme dans l'exemple
const markerGeometry = new THREE.SphereGeometry(0.015, 16, 16);
const markerMaterial = new THREE.MeshBasicMaterial({
color: config.colors.marker,
emissive: 0x000000
});
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
// Glow effect
const glowGeometry = new THREE.SphereGeometry(0.025, 16, 16);
const glowMaterial = new THREE.MeshBasicMaterial({
color: config.colors.glow,
transparent: true,
opacity: 0.3,
blending: THREE.AdditiveBlending
});
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
markerGroup.add(marker);
markerGroup.add(glow);
// Position directement sur le globe (rayon = 1)
markerGroup.position.set(x, y, z);
markerGroup.userData = site;
markersGroup.add(markerGroup);
});
// Animation variables
let autoRotate = true;
let showWave = true;
let mouseX = 0, mouseY = 0;
let targetRotationX = 0, targetRotationY = 0;
// Controls
const modeBtn = document.getElementById('modeBtn');
const waveBtn = document.getElementById('waveBtn');
const resetBtn = document.getElementById('resetBtn');
modeBtn.addEventListener('click', () => {
autoRotate = !autoRotate;
modeBtn.textContent = autoRotate ? 'Mode Manuel' : 'Mode Auto';
});
waveBtn.addEventListener('click', () => {
showWave = !showWave;
waveGroup.visible = showWave;
waveBtn.textContent = showWave ? 'Masquer Vague' : 'Afficher Vague';
});
resetBtn.addEventListener('click', () => {
targetRotationX = 0;
targetRotationY = 0;
camera.position.z = config.cameraDistance;
});
// Mouse interaction
let isDragging = false;
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
const tooltip = document.getElementById('tooltip');
renderer.domElement.addEventListener('mousedown', () => {
isDragging = true;
renderer.domElement.style.cursor = 'grabbing';
});
window.addEventListener('mouseup', () => {
isDragging = false;
renderer.domElement.style.cursor = 'grab';
});
window.addEventListener('mousemove', (event) => {
if (isDragging && !autoRotate) {
targetRotationY += event.movementX * 0.005;
targetRotationX += event.movementY * 0.005;
targetRotationX = Math.max(-Math.PI/2, Math.min(Math.PI/2, targetRotationX));
}
// Update mouse position for raycasting
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
});
// Click detection
renderer.domElement.addEventListener('click', (event) => {
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(markersGroup.children, true);
if (intersects.length > 0) {
const marker = intersects[0].object.parent;
if (marker.userData && marker.userData.name) {
const data = marker.userData;
tooltip.innerHTML = `
<strong>${data.name}</strong>
<span class="date">${data.date} à ${data.time}</span>
<br><small>Ordre: ${data.order}/${sites.length}</small>
`;
tooltip.style.left = event.clientX + 10 + 'px';
tooltip.style.top = event.clientY - 30 + 'px';
tooltip.style.display = 'block';
setTimeout(() => {
tooltip.style.display = 'none';
}, 3000);
}
}
});
// Zoom with mouse wheel
renderer.domElement.addEventListener('wheel', (event) => {
event.preventDefault();
camera.position.z += event.deltaY * 0.002;
camera.position.z = Math.max(2, Math.min(5, camera.position.z));
});
// Animation loop
function animate() {
requestAnimationFrame(animate);
// Auto-rotate
if (autoRotate) {
targetRotationY += config.autoRotateSpeed;
}
// Smooth rotation
globeGroup.rotation.y += (targetRotationY - globeGroup.rotation.y) * 0.1;
globeGroup.rotation.x += (targetRotationX - globeGroup.rotation.x) * 0.1;
// Animate wave
if (showWave) {
const time = Date.now() * 0.0002; // Même vitesse que l'exemple
// Oscillation entre sud et nord comme dans l'exemple
const latAngle = Math.sin(time) * (Math.PI / 2);
waveRings.forEach((ring, i) => {
const offset = i * 0.05; // Petit décalage entre les anneaux
// Position Y basée sur le sinus de l'angle de latitude
ring.position.y = Math.sin(latAngle + offset);
// Scale basé sur le cosinus pour maintenir la forme circulaire
const scale = Math.cos(latAngle + offset);
ring.scale.set(scale, 1, scale);
// Opacité proportionnelle à l'échelle
ring.material.opacity = (0.4 - (i * 0.1)) * Math.max(0.2, Math.abs(scale));
});
}
// Rotate stars slowly
stars.rotation.y += 0.0001;
// Animate markers
markersGroup.children.forEach((marker, i) => {
const time = Date.now() * 0.001;
const offset = i * 0.2;
const scale = 1 + Math.sin(time + offset) * 0.1;
marker.scale.setScalar(scale);
});
renderer.render(scene, camera);
}
animate();
}).catch(error => {
console.error('Error loading textures:', error);
document.getElementById('loading').innerHTML = `
<div style="color: #ff6b6b;">Erreur de chargement</div>
<div style="color: #8892b0; margin-top: 10px;">Vérifiez votre connexion internet</div>
`;
});
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Initial cursor style
renderer.domElement.style.cursor = 'grab';
</script>
</body>
</html>