测量工具 (新版)
MeasurePlugin
功能说明
MeasurePlugin 是新版测量插件,用于在空间中测量距离和面积。相比经典版 PanoMeasurePlugin,新版测量插件提供了更简洁的 API 和更好的用户体验。
关于 MeasurePlugin 和 PanoMeasurePlugin 的详细对比,请参阅 使用说明。
效果展示
快速开始
安装
npm install @realsee/dnalogel初始化
通过 Five plugins 方式初始化:
import { import MeasurePluginMeasurePlugin } from '@realsee/dnalogel'
import { class Five如视 VR Web展示器
Five } from '@realsee/five'
const const five: Fivefive = new new Five(initArgs?: FiveInitArgs | undefined): Five如视 VR Web展示器
Five({
FiveInitArgs.plugins?: (((five: Five) => void) | [Plugin: ((five: Five) => any) | ((five: Five, parameters: any) => any), instanceName: string | null] | [Plugin: ((five: Five) => any) | ((five: Five, parameters: any) => any), instanceName: string | null, parameters: any] | [Plugin: (five: Five) => any, instanceName: string | null])[] | undefined插件定义
plugins: [
[import MeasurePluginMeasurePlugin, 'measurePlugin', { unit: stringunit: 'm' }]
]
})
const const measurePlugin: anymeasurePlugin = const five: Fivefive.Five.plugins: {
[key: string]: any;
}插件暴露的方法
plugins.__type[string]: anymeasurePlugin as type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyObtain the return type of a function type
ReturnType<typeof import MeasurePluginMeasurePlugin>也可以自己初始化:
import { import MeasurePluginMeasurePlugin } from '@realsee/dnalogel'
const const measurePlugin: anymeasurePlugin = import MeasurePluginMeasurePlugin(const five: Fivefive, { unit: stringunit: 'm' })配置项
const const config: anyconfig: type MeasurePluginConfig = anyMeasurePluginConfig = {
// 测量单位:'m' (米) | 'ft' (英尺) | 'mm' (毫米)
unit: stringunit: 'm',
// 是否显示长度标签,默认 true
lengthEnable: booleanlengthEnable: true,
// 长度测量的小数位数,默认 2
precision: numberprecision: 2,
}基础用法
开始测量
const measurePlugin: anymeasurePlugin.measure()结束测量
const measurePlugin: anymeasurePlugin.endMeasure()撤销上一步
const measurePlugin: anymeasurePlugin.undo()取消本次测量
const measurePlugin: anymeasurePlugin.cancel()清空所有测量
const measurePlugin: anymeasurePlugin.clear()销毁插件
const measurePlugin: anymeasurePlugin.dispose()配置方法
切换单位
const measurePlugin: anymeasurePlugin.setUnit('ft') // 切换到英尺
const measurePlugin: anymeasurePlugin.setUnit('m') // 切换到米
const measurePlugin: anymeasurePlugin.setUnit('mm') // 切换到毫米设置精度
// 设置长度测量的小数位数
const measurePlugin: anymeasurePlugin.setPrecision(3)显示/隐藏长度标签
const measurePlugin: anymeasurePlugin.setLengthEnable(false) // 隐藏长度标签
const measurePlugin: anymeasurePlugin.setLengthEnable(true) // 显示长度标签事件监听
measureEnd 事件
measureEnd 事件是 MeasurePlugin 的核心事件,在每次测量结束时触发。通过监听此事件,您可以获取测量点坐标并进行自定义计算。
事件参数
| 参数 | 类型 | 说明 |
|---|---|---|
| reason | MeasureEndReason | 测量结束的原因 |
| points | THREE.Vector3[] | 测量点坐标数组 |
MeasureEndReason 枚举
| 值 | 说明 |
|---|---|
'enter' | 按 Enter 键完成 |
'escape' | 按 Esc 键取消 |
'polygon' | 自动完成多边形(首尾点重合) |
'mode_change' | 模式改变导致结束 |
'pano_move' | 全景移动导致结束 |
'floor_change' | 楼层改变导致结束 |
'points_insufficient' | 点数不足(少于2个点) |
'external' | 外部调用 endMeasure() 结束 |
完整使用示例
以下示例展示了如何监听 measureEnd 事件并计算测量结果(包括线段长度和多边形面积):
import { function useEffect(effect: EffectCallback, deps?: DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect } from 'react'
import * as import THREETHREE from 'three'
import { function unsafe__useFiveInstance(): FiveFive React Hooks: 获取 five 实例
unsafe__useFiveInstance } from '@realsee/five/react'
import { import UtilUtil, import validatePolygonvalidatePolygon } from '@realsee/dnalogel'
import type { import MeasurePluginMeasurePlugin } from '@realsee/dnalogel'
// 获取测量控制器类型
type type MeasureController = anyMeasureController = type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyObtain the return type of a function type
ReturnType<typeof import MeasurePluginMeasurePlugin>
// 测量结束原因类型
type type MeasureEndReason = anyMeasureEndReason = type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : neverObtain the parameters of a function type in a tuple
Parameters<type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : neverObtain the parameters of a function type in a tuple
Parameters<type MeasureController = anyMeasureController['on']>[1]>[0]
// 从 Util 中获取工具函数
const { const getGeometryInfo: anygetGeometryInfo, const generatePolygonGeometry: anygeneratePolygonGeometry } = import UtilUtil
const { const transformUnit: anytransformUnit, const transformUnitSquare: anytransformUnitSquare } = import UtilUtil.import sculptsculpt
/**
* 计算测量结果(线段长度和多边形面积)
* @param points 测量点数组
* @param unit 单位 'm' | 'ft' | 'mm'
* @param precision 精度(小数位数)
*/
function function calculateMeasurement(points: THREE.Vector3[], unit?: 'm' | 'ft' | 'mm', precision?: number): {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}计算测量结果(线段长度和多边形面积)
calculateMeasurement(points: THREE.Vector3[]测量点数组
points: import THREETHREE.class Vector33D vector.
Vector3[], unit: "m" | "ft" | "mm"单位 'm' | 'ft' | 'mm'
unit: 'm' | 'ft' | 'mm' = 'm', precision: number精度(小数位数)
precision: number = 2) {
if (points: THREE.Vector3[]测量点数组
points.Array<Vector3>.length: numberGets or sets the length of the array. This is a number one higher than the highest index in the array.
length < 2) {
return { lineLengths: never[]lineLengths: [], totalLength: nulltotalLength: null, area: nullarea: null, isPolygon: booleanisPolygon: false }
}
// 计算每条线段的长度
const const lineLengths: string[]lineLengths: string[] = []
let let totalLengthValue: numbertotalLengthValue = 0
for (let let i: numberi = 1; let i: numberi < points: THREE.Vector3[]测量点数组
points.Array<Vector3>.length: numberGets or sets the length of the array. This is a number one higher than the highest index in the array.
length; let i: numberi++) {
const const p0: THREE.Vector3p0 = points: THREE.Vector3[]测量点数组
points[let i: numberi - 1]
const const p1: THREE.Vector3p1 = points: THREE.Vector3[]测量点数组
points[let i: numberi]
const const distance: numberdistance = const p0: THREE.Vector3p0.Vector3.distanceTo(v: THREE.Vector3): numberComputes distance of this vector to v.
distanceTo(const p1: THREE.Vector3p1)
let totalLengthValue: numbertotalLengthValue += const distance: numberdistance
const const lengthStr: anylengthStr = const transformUnit: anytransformUnit(const distance: numberdistance, unit: "m" | "ft" | "mm"单位 'm' | 'ft' | 'mm'
unit, precision: number精度(小数位数)
precision)
if (const lengthStr: anylengthStr) {
const lineLengths: string[]lineLengths.Array<string>.push(...items: string[]): numberAppends new elements to the end of an array, and returns the new length of the array.
push(const lengthStr: anylengthStr)
}
}
const const totalLength: anytotalLength = const transformUnit: anytransformUnit(let totalLengthValue: numbertotalLengthValue, unit: "m" | "ft" | "mm"单位 'm' | 'ft' | 'mm'
unit, precision: number精度(小数位数)
precision)
// 检查是否为多边形(首尾点重合且点数>=3)
const const isPolygon: anyisPolygon = import validatePolygonvalidatePolygon(points: THREE.Vector3[]测量点数组
points)
let let area: string | undefinedarea: string | undefined = var undefinedundefined
if (const isPolygon: anyisPolygon) {
// 生成多边形几何体并计算面积
const const geometry: anygeometry = const generatePolygonGeometry: anygeneratePolygonGeometry(points: THREE.Vector3[]测量点数组
points)
if (const geometry: anygeometry) {
const const geometryInfo: anygeometryInfo = const getGeometryInfo: anygetGeometryInfo(const geometry: anygeometry)
if (const geometryInfo: anygeometryInfo) {
let area: string | undefinedarea = const transformUnitSquare: anytransformUnitSquare(const geometryInfo: anygeometryInfo.area, unit: "m" | "ft" | "mm"单位 'm' | 'ft' | 'mm'
unit)
}
}
}
return { lineLengths: string[]lineLengths, totalLength: anytotalLength, area: string | undefinedarea, isPolygon: anyisPolygon }
}
const const MeasurePluginUsage: () => React.JSX.ElementMeasurePluginUsage = () => {
const const five: Fivefive = function unsafe__useFiveInstance(): FiveFive React Hooks: 获取 five 实例
unsafe__useFiveInstance()
const const measurePlugin: anymeasurePlugin = const five: Fivefive.Five.plugins: {
[key: string]: any;
}插件暴露的方法
plugins.__type[string]: anymeasurePlugin as type MeasureController = anyMeasureController
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList | undefined): voidAccepts a function that contains imperative, possibly effectful code.
useEffect(() => {
// 监听测量结束事件
const const handleMeasureEnd: (reason: any, points: THREE.Vector3[]) => voidhandleMeasureEnd = (reason: anyreason: type MeasureEndReason = anyMeasureEndReason, points: THREE.Vector3[]points: import THREETHREE.class Vector33D vector.
Vector3[]) => {
var console: Consoleconsole.Console.log(...data: any[]): voidlog('=== 测量结束 ===')
var console: Consoleconsole.Console.log(...data: any[]): voidlog('结束原因:', reason: anyreason)
var console: Consoleconsole.Console.log(...data: any[]): voidlog('测量点:', points: THREE.Vector3[]points.Array<Vector3>.map<string>(callbackfn: (value: THREE.Vector3, index: number, array: THREE.Vector3[]) => string, thisArg?: any): string[]Calls a defined callback function on each element of an array, and returns an array that contains the results.
map((p: THREE.Vector3p) => `(${p: THREE.Vector3p.Vector3.x: numberx.Number.toFixed(fractionDigits?: number | undefined): stringReturns a string representing a number in fixed-point notation.
toFixed(3)}, ${p: THREE.Vector3p.Vector3.y: numbery.Number.toFixed(fractionDigits?: number | undefined): stringReturns a string representing a number in fixed-point notation.
toFixed(3)}, ${p: THREE.Vector3p.Vector3.z: numberz.Number.toFixed(fractionDigits?: number | undefined): stringReturns a string representing a number in fixed-point notation.
toFixed(3)})`))
// 计算测量结果
const const result: {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result = function calculateMeasurement(points: THREE.Vector3[], unit?: 'm' | 'ft' | 'mm', precision?: number): {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}计算测量结果(线段长度和多边形面积)
calculateMeasurement(points: THREE.Vector3[]points, 'm', 2)
var console: Consoleconsole.Console.log(...data: any[]): voidlog('--- 计算结果 ---')
var console: Consoleconsole.Console.log(...data: any[]): voidlog('是否为多边形:', const result: {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result.isPolygon: anyisPolygon)
var console: Consoleconsole.Console.log(...data: any[]): voidlog('各线段长度:', const result: {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result.lineLengths: string[] | never[]lineLengths)
var console: Consoleconsole.Console.log(...data: any[]): voidlog('总长度:', const result: {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result.totalLength: anytotalLength)
if (const result: {
lineLengths: never[];
totalLength: null;
area: null;
isPolygon: boolean;
} | {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result.area: string | null | undefinedarea) {
var console: Consoleconsole.Console.log(...data: any[]): voidlog('面积:', const result: {
lineLengths: string[];
totalLength: any;
area: string | undefined;
isPolygon: any;
}result.area: stringarea)
}
var console: Consoleconsole.Console.log(...data: any[]): voidlog('================')
}
const measurePlugin: anymeasurePlugin.on('measureEnd', const handleMeasureEnd: (reason: any, points: THREE.Vector3[]) => voidhandleMeasureEnd)
return () => {
const measurePlugin: anymeasurePlugin.off('measureEnd', const handleMeasureEnd: (reason: any, points: THREE.Vector3[]) => voidhandleMeasureEnd)
}
}, [const measurePlugin: anymeasurePlugin])
return (
<JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const measurePlugin: anymeasurePlugin.measure()}>开始测量</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const measurePlugin: anymeasurePlugin.endMeasure()}>结束测量</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const measurePlugin: anymeasurePlugin.clear()}>清空</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const measurePlugin: anymeasurePlugin.undo()}>撤销</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
<JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const measurePlugin: anymeasurePlugin.dispose()}>关闭</JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
)
}
export default const MeasurePluginUsage: () => React.JSX.ElementMeasurePluginUsage面积计算说明
MeasurePlugin 本身不直接返回面积值。要获取多边形面积,您需要使用 Util 中提供的工具函数进行计算。
计算流程
- 验证多边形:使用
validatePolygon(points)检查点集是否构成有效多边形 - 生成几何体:使用
generatePolygonGeometry(points)生成 THREE.BufferGeometry - 计算面积:使用
getGeometryInfo(geometry)获取面积信息 - 格式化输出:使用
transformUnitSquare(area, unit)转换为带单位的字符串
工具函数说明
| 函数 | 说明 |
|---|---|
validatePolygon(points) | 验证点集是否构成有效多边形(首尾点重合、不共线、共面) |
generatePolygonGeometry(points) | 根据多边形顶点生成 BufferGeometry |
getGeometryInfo(geometry) | 获取几何体信息,包括面积和中心点 |
transformUnit(value, unit, precision) | 将长度值转换为带单位的字符串 |
transformUnitSquare(value, unit) | 将面积值转换为带单位的字符串 |
代码示例
import * as import THREETHREE from 'three'
import { import UtilUtil, import validatePolygonvalidatePolygon } from '@realsee/dnalogel'
const { const getGeometryInfo: anygetGeometryInfo, const generatePolygonGeometry: anygeneratePolygonGeometry } = import UtilUtil
const { const transformUnitSquare: anytransformUnitSquare } = import UtilUtil.import sculptsculpt
// 假设这是测量得到的点
const const points: THREE.Vector3[]points: import THREETHREE.class Vector33D vector.
Vector3[] = [
new import THREETHREE.constructor Vector3(x?: number | undefined, y?: number | undefined, z?: number | undefined): THREE.Vector33D vector.
Vector3(0, 0, 0),
new import THREETHREE.constructor Vector3(x?: number | undefined, y?: number | undefined, z?: number | undefined): THREE.Vector33D vector.
Vector3(1, 0, 0),
new import THREETHREE.constructor Vector3(x?: number | undefined, y?: number | undefined, z?: number | undefined): THREE.Vector33D vector.
Vector3(1, 0, 1),
new import THREETHREE.constructor Vector3(x?: number | undefined, y?: number | undefined, z?: number | undefined): THREE.Vector33D vector.
Vector3(0, 0, 1),
new import THREETHREE.constructor Vector3(x?: number | undefined, y?: number | undefined, z?: number | undefined): THREE.Vector33D vector.
Vector3(0, 0, 0), // 闭合点
]
// 1. 验证是否为多边形
if (import validatePolygonvalidatePolygon(const points: THREE.Vector3[]points)) {
// 2. 生成几何体
const const geometry: anygeometry = const generatePolygonGeometry: anygeneratePolygonGeometry(const points: THREE.Vector3[]points)
if (const geometry: anygeometry) {
// 3. 获取面积信息
const const geometryInfo: anygeometryInfo = const getGeometryInfo: anygetGeometryInfo(const geometry: anygeometry)
if (const geometryInfo: anygeometryInfo) {
// 4. 格式化输出
const const areaString: anyareaString = const transformUnitSquare: anytransformUnitSquare(const geometryInfo: anygeometryInfo.area, 'm')
var console: Consoleconsole.Console.log(...data: any[]): voidlog('面积:', const areaString: anyareaString) // 输出: "1.00m²"
}
}
}快捷键
| 快捷键 | 功能 |
|---|---|
Enter | 完成当前测量 |
Esc | 取消当前测量 |
Ctrl/Cmd + Z | 撤销上一个点 |
Delete / Backspace | 删除选中的测量 |
Shift + Click | 在显示直角三角形辅助线时,选择垂直点而非射线交点 |
类型导出
import type { import MeasurePluginMeasurePlugin, import MeasureEndReasonMeasureEndReason, import MeasurePluginEventMapMeasurePluginEventMap, import MeasurePluginConfigMeasurePluginConfig } from '@realsee/dnalogel'// MeasurePluginConfig
type MeasurePluginConfig = {
unit: 'm' | 'ft' | 'mm'
lengthEnable?: boolean
precision?: number
}
// MeasureEndReason
type MeasureEndReason =
| 'enter'
| 'escape'
| 'polygon'
| 'mode_change'
| 'pano_move'
| 'floor_change'
| 'points_insufficient'
| 'external'
// MeasurePluginEventMap
type MeasurePluginEventMap = {
measureEnd: (reason: MeasureEndReason, points: THREE.Vector3[]) => void
undo: () => void
}