@@ -179,26 +180,58 @@ function test2() { refreshCount() } +function isLabelInView(label, frustum, cameraPosition, maxDistance = 65) { + const pos = new THREE.Vector3() + label.getWorldPosition(pos) + + // 正交相机的视锥体范围 + if (frustum.containsPoint(pos)) { + // 检查标签是否在相机位置的最大距离内 + if (shouldShowLabel(label)) { + return true + } + } + return false +} + +const labels: Text[] = [] + +function getLabelPixelSize(fontSize, cameraZoom) { + const pixelRatio = renderer.getPixelRatio() + const referenceZoom = 1 + const referenceFontSize = 0.2 + const referencePixelSize = 16 // fontSize=0.2, zoom=1 时显示为 16px + + const scale = (fontSize / referenceFontSize) * (cameraZoom / referenceZoom) + return referencePixelSize * scale * pixelRatio +} + +function shouldShowLabel(label, minPixelSize = 700) { + const pixelSize = getLabelPixelSize(label.fontSize, camera.zoom) + console.log(`pixel size: ${pixelSize}`) + return pixelSize >= minPixelSize +} + /** - * BufferGeometry + Label + * InstanceMesh(Point) + BufferGeometry + Label */ function test3() { - cleanupThree() - const xcount = 100 + cleanupThree() // 清空画布 + const xcount = 300 const zcount = 100 const dist = 1.25 const spacing = dist const y = 0.1 - const noShaderMaterial = new THREE.MeshBasicMaterial({ color: 0xFFFF99, transparent: true, depthWrite: false, side: THREE.DoubleSide }) - const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25) // 设置大小 + const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25) + // 使用 InstancedMesh 网格优化 const instancedMesh = new THREE.InstancedMesh(planeGeometry, noShaderMaterial, zcount * xcount) instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) const dummy = new THREE.Object3D() @@ -212,7 +245,7 @@ function test3() { const pz = z * dist dummy.position.set(px, y, pz) - dummy.rotation.set(-Math.PI / 2, 0, 0) // 假设你想让平面朝上,相当于面对相机视角的一部分 + dummy.rotation.set(-Math.PI / 2, 0, 0) dummy.updateMatrix() instancedMesh.setMatrixAt(z * xcount + x, dummy.matrix) @@ -222,9 +255,9 @@ function test3() { scene.add(instancedMesh) const positions = [] - const labels = [] + labels.length = 0 - function createTextLabel(text, position) { + function createTextLabel(text, position): Text { const label = new Text() label.text = text label.font = SimSunTTF @@ -243,7 +276,8 @@ function test3() { label.position.set(position.x, position.y + 0.3, position.z - 0.2) label.quaternion.copy(camera.quaternion) - label.sync() + label.visible = false + // label.sync() return label } @@ -290,8 +324,9 @@ function test3() { lineSegments.name = 'grid-lines' scene.add(lineSegments) - // labels.forEach(label => scene.add(label)) + labels.forEach(label => scene.add(label)) + // 统计总数量 refreshCount() } @@ -299,6 +334,7 @@ function test3() { const restate = reactive({ targetColor: '#ff0000', loadScale: 1, + viewLabelCount: 0, mode: 'translate', objects: 0, vertices: 0, @@ -399,16 +435,52 @@ function initThree() { statsControls.dom.style.left = 'auto' viewerDom.appendChild(statsControls.dom) - renderer.setAnimationLoop(renderView) + renderer.setAnimationLoop(animate) renderer.setClearColor(0x000000, 0) // animate() } // 动画循环 +let frameCount = 0 + function animate() { - animationFrameId = requestAnimationFrame(animate) + // animationFrameId = requestAnimationFrame(animate) renderView() + + if (frameCount++ % 60 === 0) { // 每 60 帧更新一次文本 + const frustum = new THREE.Frustum() + const cameraCopy = camera.clone() + + // 必须更新相机的世界矩阵 + cameraCopy.updateMatrixWorld() + + // 构造投影矩阵 + const projScreenMatrix = new THREE.Matrix4().multiplyMatrices( + cameraCopy.projectionMatrix, + cameraCopy.matrixWorldInverse + ) + + // 设置视锥体 + frustum.setFromProjectionMatrix(projScreenMatrix) + + let viewLabelCount = 0 + labels.forEach((label: Text) => { + // label.quaternion.copy(camera.quaternion) // billboard 效果保持朝向相机 + const isvis = isLabelInView(label, frustum, camera.position) + if (isvis) { + viewLabelCount++ + } + if (isvis && label.visible === false) { + label.visible = true + label.sync() + } else if (!isvis && label.visible === true) { + label.visible = false + label.sync() + } + }) + restate.viewLabelCount = viewLabelCount + } } function handleResize(entries) {