//#region assets/images/home/earth_light_gray_white.png var earth_light_gray_white_default = "" + new URL("../images/earth_light_gray_white-D7Fpf71y.png", import.meta.url).href; //#endregion //#region assets/images/about/location.svg var location_default = "data:image/svg+xml,%3csvg%20viewBox='0%200%2030%2041.1846'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20width='30.000000'%20height='41.184570'%20fill='none'%20customFrame='%23000000'%3e%3cpath%20id='减去顶层'%20d='M14.898%2041.1845C14.898%2041.1845%2030%2031.7422%2030%2014.8666C30%206.6297%2023.2653%200%2015.102%200C6.73469%200%200%206.6297%200%2014.8666C0%2030.5368%2014.898%2041.1845%2014.898%2041.1845ZM20.2041%2015.0225C20.2041%2017.7964%2017.9198%2020.045%2015.102%2020.045C12.2843%2020.045%2010%2017.7964%2010%2015.0225C10%2012.2487%2012.2843%2010%2015.102%2010C17.9198%2010%2020.2041%2012.2487%2020.2041%2015.0225Z'%20fill='rgb(239,67,53)'%20fill-rule='evenodd'%20/%3e%3c/svg%3e"; //#endregion //#region js/earth.js (function() { const canvas = document.getElementById("earth-canvas"); if (!canvas || !window.THREE) return; const container = canvas.parentElement; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(40, container.clientWidth / container.clientHeight, .1, 1e3); camera.position.z = 1.5; const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true }); renderer.setSize(container.clientWidth, container.clientHeight); renderer.setPixelRatio(window.devicePixelRatio); const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = .05; controls.enablePan = true; controls.enableZoom = false; controls.autoRotate = false; controls.rotateSpeed = .1; controls.minPolarAngle = Math.PI / 2; controls.maxPolarAngle = Math.PI / 2; const geometry = new THREE.SphereGeometry(.5, 64, 64); const hemiLight = new THREE.HemisphereLight("#e0e0e0", "#6a8aaa", .5); scene.add(hemiLight); const bounceLight = new THREE.DirectionalLight("#000000", .1); bounceLight.position.set(0, -5, 0); scene.add(bounceLight); const ambientLight = new THREE.AmbientLight("#e0e0e0", .9); scene.add(ambientLight); const textureLoader = new THREE.TextureLoader(); const material = new THREE.MeshPhongMaterial({ color: "#efefef", emissive: "#000000", shininess: 20, transparent: true, opacity: .9 }); const earth = new THREE.Mesh(geometry, material); earth.renderOrder = 0; textureLoader.load(earth_light_gray_white_default, function(texture) { material.map = texture; material.color.setHex(16777215); material.needsUpdate = true; }, void 0, function(err) { console.warn("Earth texture failed to load, using default color.", err); }); scene.add(earth); const markersData = [ { name: "上海", lat: 31.2304, lon: 121.4737 }, { name: "东京", lat: 35.6762, lon: 139.6503 }, { name: "伊斯坦布尔", lat: 41.0082, lon: 28.9784 }, { name: "伦敦", lat: 51.5074, lon: -.1278 }, { name: "加尔各答", lat: 22.5726, lon: 88.3639 }, { name: "北京", lat: 39.9042, lon: 116.4074 }, { name: "华沙", lat: 52.2297, lon: 21.0122 }, { name: "南京", lat: 32.0603, lon: 118.7969 }, { name: "吉隆坡", lat: 3.139, lon: 101.6869 }, { name: "圣保罗", lat: -23.5505, lon: -46.6333 }, { name: "圣安东尼奥", lat: 29.4241, lon: -98.4936 }, { name: "墨西哥城", lat: 19.4326, lon: -99.1332 }, { name: "多伦多", lat: 43.6532, lon: -79.3832 }, { name: "奥克兰", lat: -36.8485, lon: 174.7633 }, { name: "孟买", lat: 19.076, lon: 72.8777 }, { name: "安地比斯", lat: 43.5804, lon: 7.1251 }, { name: "山景城", lat: 37.3861, lon: -122.0839 }, { name: "巴黎", lat: 48.8566, lon: 2.3522 }, { name: "布宜诺斯艾利斯", lat: -34.6037, lon: -58.3816 }, { name: "开普敦", lat: -33.9249, lon: 18.4241 }, { name: "悉尼", lat: -33.8688, lon: 151.2093 }, { name: "新加坡", lat: 1.3521, lon: 103.8198 }, { name: "新德里", lat: 28.6139, lon: 77.209 }, { name: "曼谷", lat: 13.7563, lon: 100.5018 }, { name: "法兰克福", lat: 50.1109, lon: 8.6821 }, { name: "波哥大", lat: 4.711, lon: -74.0721 }, { name: "深圳", lat: 22.5431, lon: 114.0579 }, { name: "牛津", lat: 51.752, lon: -1.2577 }, { name: "特拉维夫", lat: 32.0853, lon: 34.7818 }, { name: "科伦坡", lat: 6.9271, lon: 79.8612 }, { name: "米兰", lat: 45.4642, lon: 9.19 }, { name: "纽约", lat: 40.7128, lon: -74.006 }, { name: "莫斯科", lat: 55.7558, lon: 37.6173 }, { name: "达卡", lat: 23.8103, lon: 90.4125 }, { name: "迪拜", lat: 25.2048, lon: 55.2708 }, { name: "邦加罗尔", lat: 12.9716, lon: 77.5946 }, { name: "金奈", lat: 13.0827, lon: 80.2707 }, { name: "雅加达", lat: -6.2088, lon: 106.8456 }, { name: "首尔", lat: 37.5665, lon: 126.978 }, { name: "香港", lat: 22.3193, lon: 114.1694 }, { name: "全球总部福州", lat: 26.0745, lon: 119.2965 } ]; const markerMeshes = []; let fuzhouMarkerMesh = null; function latLonToVector3(lat, lon, radius = 1) { const phi = (90 - lat) * (Math.PI / 180); const theta = (lon + 180) * (Math.PI / 180); const x = -radius * Math.sin(phi) * Math.cos(theta); const y = radius * Math.cos(phi); const z = radius * Math.sin(phi) * Math.sin(theta); return new THREE.Vector3(x, y, z); } const defaultMarkerGeometry = new THREE.CircleGeometry(.012, 24); const defaultMarkerMaterial = new THREE.MeshBasicMaterial({ color: 15680309, side: THREE.FrontSide, depthWrite: false, transparent: true, opacity: 1 }); const svgWidth = .03; const svgHeight = .04; const locationTexture = textureLoader.load(location_default); const fuzhouMarkerGeometry = new THREE.PlaneGeometry(svgWidth, svgHeight); fuzhouMarkerGeometry.translate(0, svgHeight / 2, 0); const fuzhouMarkerMaterial = new THREE.MeshBasicMaterial({ map: locationTexture, color: 16777215, side: THREE.FrontSide, depthWrite: false, transparent: true, opacity: 1 }); markersData.forEach((data) => { const position = latLonToVector3(data.lat, data.lon, .5); let marker; if (data.name === "全球总部福州") { marker = new THREE.Mesh(fuzhouMarkerGeometry, fuzhouMarkerMaterial); fuzhouMarkerMesh = marker; } else marker = new THREE.Mesh(defaultMarkerGeometry, defaultMarkerMaterial); marker.position.copy(position); const normal = position.clone().normalize(); marker.position.add(normal.multiplyScalar(.01)); marker.renderOrder = 1; marker.userData = { name: data.name }; earth.add(marker); markerMeshes.push(marker); }); const targetLon = 9.6869; earth.rotation.y = -(targetLon * Math.PI / 180) + Math.PI; const tooltip = document.createElement("div"); tooltip.style.position = "absolute"; tooltip.style.padding = "8px"; tooltip.style.color = "#2E353A"; tooltip.style.fontSize = "16px"; tooltip.style.fontFamily = "MiSans VF"; tooltip.style.textAlign = "center"; tooltip.style.lineHeight = "20px"; tooltip.style.whiteSpace = "nowrap"; tooltip.style.pointerEvents = "none"; tooltip.style.display = "none"; tooltip.style.zIndex = "1000"; tooltip.style.transform = "translate(-50%, 15px)"; tooltip.classList.add("tooltip-arrow"); container.appendChild(tooltip); const fuzhouTooltip = tooltip.cloneNode(true); fuzhouTooltip.innerHTML = "全球总部
福州"; container.appendChild(fuzhouTooltip); const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); canvas.addEventListener("mousemove", (e) => { const rect = canvas.getBoundingClientRect(); mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1; mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(markerMeshes); if (intersects.length > 0) { const object = intersects[0].object; const name = object.userData.name; if (name !== "全球总部福州") { tooltip.textContent = name; tooltip.style.display = "block"; const vector = object.position.clone(); object.updateWorldMatrix(true, false); vector.setFromMatrixPosition(object.matrixWorld); vector.project(camera); const containerRect = container.getBoundingClientRect(); const x = (vector.x * .5 + .5) * rect.width + (rect.left - containerRect.left); const y = (-(vector.y * .5) + .5) * rect.height + (rect.top - containerRect.top); tooltip.style.left = `${x}px`; tooltip.style.top = `${y - 10}px`; } else tooltip.style.display = "none"; document.body.style.cursor = "pointer"; } else { tooltip.style.display = "none"; document.body.style.cursor = "default"; } }); function animate() { requestAnimationFrame(animate); earth.rotation.y += 5e-4; markerMeshes.forEach((marker) => { marker.lookAt(camera.position); const worldPosition = new THREE.Vector3(); marker.getWorldPosition(worldPosition); const viewDirection = new THREE.Vector3().subVectors(camera.position, worldPosition).normalize(); const dot = worldPosition.clone().normalize().dot(viewDirection); let scaleValue = Math.max(0, dot); scaleValue = Math.pow(scaleValue, .5); marker.scale.setScalar(scaleValue); }); if (fuzhouMarkerMesh) { const worldPosition = new THREE.Vector3(); fuzhouMarkerMesh.getWorldPosition(worldPosition); const viewDirection = new THREE.Vector3().subVectors(camera.position, worldPosition).normalize(); if (worldPosition.clone().normalize().dot(viewDirection) > 0) { fuzhouTooltip.style.display = "block"; const vector = fuzhouMarkerMesh.position.clone(); fuzhouMarkerMesh.updateWorldMatrix(true, false); vector.setFromMatrixPosition(fuzhouMarkerMesh.matrixWorld); vector.project(camera); const rect = canvas.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const x = (vector.x * .5 + .5) * rect.width + (rect.left - containerRect.left); const y = (-(vector.y * .5) + .5) * rect.height + (rect.top - containerRect.top); fuzhouTooltip.style.left = `${x}px`; fuzhouTooltip.style.top = `${y - 10}px`; } else fuzhouTooltip.style.display = "none"; } controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener("resize", () => { const width = container.clientWidth; const height = container.clientHeight; camera.aspect = width / height; camera.updateProjectionMatrix(); renderer.setSize(width, height); }); })(); //#endregion