|
|
|
@ -7,7 +7,8 @@ |
|
|
|
<div class="demo-color-block"> |
|
|
|
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span> |
|
|
|
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span> |
|
|
|
<span class="demonstration"> 三角形数:<el-text type="danger">{{ restate.faces }}</el-text></span> |
|
|
|
<span class="demonstration"> 三角形:<el-text type="danger">{{ restate.faces }}</el-text></span> |
|
|
|
<span class="demonstration"> 标签:<el-text type="danger">{{ restate.viewLabelCount }}</el-text></span> |
|
|
|
</div> |
|
|
|
</el-space> |
|
|
|
<div class="main-content"> |
|
|
|
@ -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) { |
|
|
|
|