Three.js地球模型标记仅映射半个球体问题求助
问题:Three.js地球模型标记仅显示半个地球,无法跨越“接缝”
基于Three.js实现带地图纹理的地球模型,标记通过经纬度映射到球面上,但发现标记仅能显示在半个地球上,手动调整标记位置也无法越过EAS标记旁的“接缝”。
核心代码
初始化代码
EarthView3D.Init = function (siteObjects, animateCallback) { const EARTH_RADIUS = 225; const EARTH_SEGMENTS = 75; const EARTH_RINGS = 75; camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000); camera.position.set(-70, 375, 500); scene = new THREE.Scene(); var light = new THREE.PointLight(0xffffff, 1, Infinity); camera.add(light); scene.add(camera); // Starfield var starGeometry = new THREE.SphereGeometry(1000, 50, 50); //basically creates another sphere around the earth's sphere, and covers it in starfield image. var starMaterial = new THREE.MeshPhongMaterial({ map: new THREE.ImageUtils.loadTexture(Global.basePath + 'Content/images/galaxy_starfield.png'), side: THREE.DoubleSide, shininess: 0 }); var starField = new THREE.Mesh(starGeometry, starMaterial); scene.add(starField); //paths to each of the texture images. var earthNoCloudsTex = Global.basePath + 'Content/images/earth_no_clouds_12k.jpg'; var earthDarkTex = Global.basePath + 'Content/images/earth_dark_12k.jpg'; var earthDarkInvertTex = Global.basePath + 'Content/images/earth_dark_invert_12k.jpg'; var earthRedTex = Global.basePath + 'Content/images/earth_dark_red_12k.jpg'; // Set to default texture, earth with no clouds. var currentTexture = earthNoCloudsTex; //Create the Earth's sphere and give it an initial texture image var earthSphere = new THREE.SphereGeometry(EARTH_RADIUS, EARTH_SEGMENTS, EARTH_RINGS); var earthMaterial = new THREE.MeshBasicMaterial({ map: new THREE.ImageUtils.loadTexture(Global.basePath + 'Content/images/earth_no_clouds_12k.jpg') }); var theEarth = new THREE.Mesh(earthSphere, earthMaterial); //mesh them together, to create the earth. scene.add(theEarth); //add earth to the scene. //Adding 1st atmosphere, inner var innerAtmosphere = new THREE.SphereGeometry(EARTH_RADIUS, EARTH_SEGMENTS, EARTH_RINGS); var atmosMaterial = THREEx.createAtmosphereMaterial(); atmosMaterial.uniforms.glowColor.value.set(0x00b3ff); atmosMaterial.uniforms.coeficient.value = 1; atmosMaterial.uniforms.power.value = 1.8; var atmosMesh = new THREE.Mesh(innerAtmosphere, atmosMaterial); atmosMesh.scale.multiplyScalar(1.001); scene.add(atmosMesh); //Adding 2nd atmosphere, the outer faded looking ring around inner atmosphere. var outerAtmosphere = new THREE.SphereGeometry(EARTH_RADIUS, EARTH_SEGMENTS, EARTH_RINGS); var atmosMaterial2 = THREEx.createAtmosphereMaterial(); atmosMaterial2.side = THREE.BackSide; atmosMaterial2.uniforms.glowColor.value.set(0x00b3ff); atmosMaterial2.uniforms.coeficient.value = .800; atmosMaterial2.uniforms.power.value = 4.0; var atmosMesh2 = new THREE.Mesh(outerAtmosphere, atmosMaterial2); atmosMesh2.scale.multiplyScalar(1.06); scene.add(atmosMesh2); //Add toggle click event for changing earth background texture $('.texture_change_button').on("click", function (e) { //figure out which button was clicked var buttonClicked = $(this).attr('id'); //disable all currently disabled buttons before disabling another $(" :disabled").attr('disabled', false); //disable button clicked so that it can't be clicked twice. $(this).attr('disabled', true); //depending on the id of the buttonClicked, update the currentTexture switch (buttonClicked) { case 'earth_no_clouds_button': currentTexture = earthNoCloudsTex; break; case 'earth_dark_button': currentTexture = earthDarkTex; break; case 'earth_dark_invert_button': currentTexture = earthDarkInvertTex; break; case 'earth_red_button': currentTexture = earthRedTex; } //switch out the image with new currentTexture earthMaterial.map = THREE.ImageUtils.loadTexture(currentTexture); theEarth.material.needsUpdate = true; scene.add(theEarth); }); // Add siteObjects to Earth for (var i = 0; i < siteObjects.length; i++) { var siteID = siteObjects[i].SiteID; var name = siteObjects[i].Name; var lat = siteObjects[i].LAT; var lon = siteObjects[i].LON; var status = siteObjects[i].Status; // Create dot for each site var siteDiv = document.createElement('div'); siteDiv.setAttribute("id", name); //set data attributes to later use when clicking dot. siteDiv.setAttribute("data-siteid", siteID); siteDiv.setAttribute("title", name); siteDiv.className = status.toLowerCase() == "fmc" ? "site_status fmc" : status.toLowerCase() == "pmc" ? "site_status pmc" : "site_status nmc"; //Create label to append to each site's dot var siteDivLabel = document.createElement('label'); siteDivLabel.setAttribute("id", name + '_label'); siteDivLabel.setAttribute("title", name + ' label'); siteDivLabel.textContent = name; //Setting the label's class (Some labels need to display on different sides of the dot so that all can be seen in initial view.) if (siteID == 6 || siteID == 9) { // 6 is BUckley, 9 is COD siteDivLabel.className = 'label_top_right earth_view_label'; } else if (siteID == 12 || siteID == 20 || siteID == 19) { // 12 is FGA, 20 is PAFB, 19 is MDIOC siteDivLabel.className = 'label_bottom_right earth_view_label'; } else if (siteID == 8) { // 8 is CMD siteDivLabel.className = 'label_bottom_left earth_view_label'; } else { siteDivLabel.className = 'label_top_left earth_view_label'; } siteDiv.appendChild(siteDivLabel); var siteDot = new THREE.CSS2DObject(siteDiv); siteDiv.parent = siteDot; siteDot.element.style.cursor = "pointer"; // Add click event for each dot/label that takes them to the site section for the dashboard homepage. siteDot.element.onclick = function () { var siteSK = $(this).data('siteid'); EarthView3D.Edit(siteSK); }; var sitePosition = EarthView3D.CalcPosFromLatLonRad(lat, lon, EARTH_RADIUS); // Some code to slightly move certain site markers around from their original position so that they don't overlap each other too much. if (siteID == 5) { // 5 is boulder siteDot.position.set(sitePosition[0] - 1, sitePosition[1] + 1, Math.abs(sitePosition[2])); } else if (siteID == 8) { // 8 is CMD siteDot.position.set(sitePosition[0] - 1, sitePosition[1] - 1, Math.abs(sitePosition[2])); } else if (siteID == 11) { //11 is EAS siteDot.position.set(sitePosition[0], sitePosition[1] + 1, Math.abs(sitePosition[2])); }else if (siteID == 20) { // 20 is PAFB siteDot.position.set(sitePosition[0] + 1, sitePosition[1] - 6, Math.abs(sitePosition[2])); } else if (siteID == 19) { // 19 is MDIOC siteDot.position.set(sitePosition[0] + 5, sitePosition[1] + 4, Math.abs(sitePosition[2])); } else if (siteID == 6) { // 6 is Buckley siteDot.position.set(sitePosition[0] + 2, sitePosition[1] + 3, Math.abs(sitePosition[2])); } else { siteDot.position.set(sitePosition[0], sitePosition[1], Math.abs(sitePosition[2])); } scene.add(siteDot); } renderer = new THREE.WebGLRenderer({ alpha: true }); renderer.setClearColor(0xffffff, 0); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); var earthContainer = document.getElementById('earth_container'); earthContainer.appendChild(renderer.domElement); labelRenderer = new THREE.CSS2DRenderer(); labelRenderer.setSize(window.innerWidth, window.innerHeight); labelRenderer.domElement.style.position = 'absolute'; labelRenderer.domElement.style.top = 0; document.body.appendChild(labelRenderer.domElement); // For rotation and mouse controls var controls = new THREE.OrbitControls(camera, labelRenderer.domElement); controls.minDistance = 400; controls.maxDistance = 800; animateCallback(siteObjects); //Running Animate right after Init. };
经纬度转标记位置代码
EarthView3D.CalcPosFromLatLonRad = function (lat, lon, radius) { var phi = (90 - lat) * (Math.PI / 180) var theta = (lon + 180) * (Math.PI / 180) x = -((radius) * Math.sin(phi) * Math.cos(theta)) z = ((radius) * Math.sin(phi) * Math.sin(theta)) y = ((radius) * Math.cos(phi)) return [x, y, z]; };
问题原因
所有标记的z坐标都被强制取了绝对值Math.abs(sitePosition[2])。经纬度转换函数CalcPosFromLatLonRad会根据经度计算出正负不同的z值:当经度在0180度时z为正,经度在-1800度时z为负。但取绝对值后,所有标记的z坐标都变成了正数,相当于把所有标记都“压”到了z轴正方向的半球上,自然只能显示半个地球,且无法越过z=0的边界(也就是你说的“接缝”)。
解决方案
去掉所有设置标记位置时对z坐标的Math.abs()处理,直接使用计算出来的原始sitePosition[2]值。修改后的代码片段如下:
var sitePosition = EarthView3D.CalcPosFromLatLonRad(lat, lon, EARTH_RADIUS); // Some code to slightly move certain site markers around from their original position so that they don't overlap each other too much. if (siteID == 5) { // 5 is boulder siteDot.position.set(sitePosition[0] - 1, sitePosition[1] + 1, sitePosition[2]); } else if (siteID == 8) { // 8 is CMD siteDot.position.set(sitePosition[0] - 1, sitePosition[1] - 1, sitePosition[2]); } else if (siteID == 11) { //11 is EAS siteDot.position.set(sitePosition[0], sitePosition[1] + 1, sitePosition[2]); }else if (siteID == 20) { // 20 is PAFB siteDot.position.set(sitePosition[0] + 1, sitePosition[1] - 6, sitePosition[2]); } else if (siteID == 19) { // 19 is MDIOC siteDot.position.set(sitePosition[0] + 5, sitePosition[1] + 4, sitePosition[2]); } else if (siteID == 6) { // 6 is Buckley siteDot.position.set(sitePosition[0] + 2, sitePosition[1] + 3, sitePosition[2]); } else { siteDot.position.set(sitePosition[0], sitePosition[1], sitePosition[2]); }
修改后,标记会根据经纬度正确映射到整个地球的球面上,不再受限于半个地球,也能正常跨越经度±180度的区域。
内容的提问来源于stack exchange,提问作者JakeFromStateFarm
相关产品推荐
相关产品推荐

