|
@@ -0,0 +1,263 @@
|
|
|
|
|
+import * as Three from 'three'
|
|
|
|
|
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
|
|
|
|
+
|
|
|
|
|
+export interface MapOption {
|
|
|
|
|
+ cameraP: { X: number, Y: number, Z: number },
|
|
|
|
|
+ isRotate: boolean
|
|
|
|
|
+}
|
|
|
|
|
+export default class ThreeService {
|
|
|
|
|
+ renderer = null as unknown as Three.WebGLRenderer // 渲染器
|
|
|
|
|
+
|
|
|
|
|
+ scene = null as unknown as Three.Scene // 场景
|
|
|
|
|
+
|
|
|
|
|
+ OrbitControls = null as unknown as OrbitControls // 控制器
|
|
|
|
|
+
|
|
|
|
|
+ camera = null as unknown as Three.PerspectiveCamera // 相机
|
|
|
|
|
+
|
|
|
|
|
+ dom = null as unknown as HTMLElement
|
|
|
|
|
+
|
|
|
|
|
+ THREE = Three
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 初始化
|
|
|
|
|
+ * @param domID 原始domID
|
|
|
|
|
+ * @param OPTION cameraP相机位置+cameraL相机指向位置+LOS视距
|
|
|
|
|
+ */
|
|
|
|
|
+ initThree(domID: string, OPTION?: MapOption) {
|
|
|
|
|
+ // 默认参数
|
|
|
|
|
+ const { cameraP, isRotate } = {
|
|
|
|
|
+ cameraP: { X: 0, Y: 0, Z: 18 },
|
|
|
|
|
+ isRotate: true,
|
|
|
|
|
+ ...OPTION
|
|
|
|
|
+ }
|
|
|
|
|
+ const dom = document.getElementById(domID)
|
|
|
|
|
+ // 场景
|
|
|
|
|
+ this.scene = new Three.Scene()
|
|
|
|
|
+ // 渲染器+透明度
|
|
|
|
|
+ this.renderer = new Three.WebGLRenderer({
|
|
|
|
|
+ alpha: true,
|
|
|
|
|
+ antialias: true
|
|
|
|
|
+ })
|
|
|
|
|
+ this.renderer.setPixelRatio(window.devicePixelRatio) // 清晰度
|
|
|
|
|
+ this.renderer.setSize(dom!.clientWidth, dom!.clientHeight) // 场景大小
|
|
|
|
|
+ dom!.appendChild(this.renderer.domElement) // 元素中插入canvas对象
|
|
|
|
|
+ // 相机
|
|
|
|
|
+ this.camera = new Three.PerspectiveCamera(40, dom!.clientWidth / dom!.clientHeight, 1, 10000) // 透视相机
|
|
|
|
|
+ this.camera.position.set(cameraP.X, cameraP.Y, cameraP.Z)
|
|
|
|
|
+ this.camera.lookAt(new Three.Vector3(0, 0, 0))
|
|
|
|
|
+ // 控制器
|
|
|
|
|
+ this.OrbitControls = new OrbitControls(this.camera, this.renderer.domElement) // 控制器
|
|
|
|
|
+ this.OrbitControls.update()
|
|
|
|
|
+ this.OrbitControls.enabled = isRotate // 启用控制
|
|
|
|
|
+ // this.OrbitControls.enableRotate = true // 启用左键控制
|
|
|
|
|
+ // // this.OrbitControls.autoRotate = true //自动旋转
|
|
|
|
|
+ // this.OrbitControls.enableZoom = true // 启用缩放
|
|
|
|
|
+ // this.OrbitControls.maxDistance = 500 // 最大范围
|
|
|
|
|
+ // this.OrbitControls.minDistance = 600 // 最小范围
|
|
|
|
|
+ this.dom = dom as HTMLElement
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 鼠标点击事件
|
|
|
|
|
+ * @returns data点中的元素
|
|
|
|
|
+ */
|
|
|
|
|
+ onMouseDblclick() {
|
|
|
|
|
+ let data = null
|
|
|
|
|
+ this.dom.addEventListener(
|
|
|
|
|
+ 'click',
|
|
|
|
|
+ (event) => {
|
|
|
|
|
+ // 获取所有点中模型对象
|
|
|
|
|
+ const intersects = this.getIntersects(event, this.dom)
|
|
|
|
|
+ // 获取选中最近的 Mesh 对象
|
|
|
|
|
+ if (intersects.length !== 0 && intersects[0].object instanceof Three.Mesh) {
|
|
|
|
|
+ const selectObject = intersects[0].object
|
|
|
|
|
+ data = selectObject
|
|
|
|
|
+ console.log('选中', selectObject)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data = null
|
|
|
|
|
+ console.log('未选中 Mesh!')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //
|
|
|
|
|
+ const a = new Three.Vector3()
|
|
|
|
|
+ a.setFromMatrixPosition(this.camera.matrixWorld)
|
|
|
|
|
+ const b = new Three.Euler()
|
|
|
|
|
+ b.setFromQuaternion(this.camera.quaternion)
|
|
|
|
|
+ console.log('相机位置角度', a, b)
|
|
|
|
|
+ },
|
|
|
|
|
+ false
|
|
|
|
|
+ )
|
|
|
|
|
+ return data
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取与射线相交的对象数组
|
|
|
|
|
+ * @param event 事件
|
|
|
|
|
+ * @param {*} dom
|
|
|
|
|
+ * @returns 返回选中的对象
|
|
|
|
|
+ */
|
|
|
|
|
+ getIntersects(event: MouseEvent, dom: HTMLElement) {
|
|
|
|
|
+ event.preventDefault()
|
|
|
|
|
+ const raycaster = new Three.Raycaster()
|
|
|
|
|
+ const mouse = new Three.Vector2()
|
|
|
|
|
+ // 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
|
|
|
|
|
+ // event.offsetX为模块所在位置
|
|
|
|
|
+ mouse.x = (event.offsetX / dom.clientWidth) * 2 - 1
|
|
|
|
|
+ mouse.y = -(event.offsetY / dom.clientHeight) * 2 + 1
|
|
|
|
|
+ // 通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
|
|
|
|
|
+ raycaster.setFromCamera(mouse, this.camera)
|
|
|
|
|
+ // 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
|
|
|
|
|
+ const intersects = raycaster.intersectObjects(this.scene.children, true)
|
|
|
|
|
+ // 返回选中的对象
|
|
|
|
|
+ return intersects
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 改变对象材质属性
|
|
|
|
|
+ * @param object
|
|
|
|
|
+ */
|
|
|
|
|
+ changeMaterial(object: { material: Three.MeshLambertMaterial; children: string | any[]; }) {
|
|
|
|
|
+ const color = 0xffffff * Math.random()
|
|
|
|
|
+ const material = new Three.MeshLambertMaterial({
|
|
|
|
|
+ color,
|
|
|
|
|
+ transparent: !object.material.transparent,
|
|
|
|
|
+ opacity: 0.8
|
|
|
|
|
+ })
|
|
|
|
|
+ object.material = material
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 挤压成型
|
|
|
|
|
+ * @param arrList
|
|
|
|
|
+ * @param zoom
|
|
|
|
|
+ * @param depth
|
|
|
|
|
+ * @param OBJOPTION
|
|
|
|
|
+ * @returns
|
|
|
|
|
+ */
|
|
|
|
|
+ ExtrusionForming(arrList: { x: number, y: number }[], zoom: number, depth: any, OBJOPTION: Three.MeshBasicMaterialParameters | undefined) {
|
|
|
|
|
+ const shape = new Three.Shape()
|
|
|
|
|
+ // 绘制轮廓
|
|
|
|
|
+ for (let j = 0; j < arrList.length; j++) {
|
|
|
|
|
+ const item = arrList[j]
|
|
|
|
|
+ if (j === 0) {
|
|
|
|
|
+ shape.moveTo(item.x * zoom, item.y * zoom)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ shape.lineTo(item.x * zoom, item.y * zoom)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 挤压参数
|
|
|
|
|
+ const extrudesettings = {
|
|
|
|
|
+ steps: 2, // 细分数
|
|
|
|
|
+ depth, // 深度
|
|
|
|
|
+ bevelEnabled: false, // 是否斜角
|
|
|
|
|
+ bevelThickness: 1, // 斜角厚度
|
|
|
|
|
+ bevelSize: 0.5, // 斜角延申距离
|
|
|
|
|
+ bevelOffset: 0, // 边距
|
|
|
|
|
+ bevelSegments: 1 // 斜角分层数
|
|
|
|
|
+ }
|
|
|
|
|
+ const geometry = new Three.ExtrudeGeometry(shape, extrudesettings)
|
|
|
|
|
+
|
|
|
|
|
+ // 创建模型
|
|
|
|
|
+ const material = new Three.MeshBasicMaterial(OBJOPTION)
|
|
|
|
|
+
|
|
|
|
|
+ // const material = new Three.MeshPhongMaterial( { color: '#000' } )
|
|
|
|
|
+ // const img = new Three.TextureLoader().load('....')
|
|
|
|
|
+ // material.map = img
|
|
|
|
|
+ const mesh = new Three.Mesh(geometry, material)
|
|
|
|
|
+ return mesh
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * svg 路径转点位置
|
|
|
|
|
+ * svg path 画图 有方向判别,自行做判断
|
|
|
|
|
+ * @param DOMID DOM ID
|
|
|
|
|
+ * @returns overarr
|
|
|
|
|
+ */
|
|
|
|
|
+ SvgPathToPrint(DOMID: string) {
|
|
|
|
|
+ const svg = document.getElementById(DOMID)
|
|
|
|
|
+ const arrList = [] as any
|
|
|
|
|
+ for (let j = 0; j < svg!.childNodes.length; j++) {
|
|
|
|
|
+ const item = svg!.childNodes[j] as any
|
|
|
|
|
+ const obj = {
|
|
|
|
|
+ id: j,
|
|
|
|
|
+ list: []
|
|
|
|
|
+ } as any
|
|
|
|
|
+ if (item.nodeName === 'path') {
|
|
|
|
|
+ const length = item.getTotalLength()
|
|
|
|
|
+ for (let k = 0; k < length; k += 20) {
|
|
|
|
|
+ obj.list.push(item.getPointAtLength(k))
|
|
|
|
|
+ }
|
|
|
|
|
+ obj.list.push(item.getPointAtLength(length))
|
|
|
|
|
+ arrList.push(obj)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const newARR = [ arrList[0], arrList[1], arrList[2], arrList[3] ]
|
|
|
|
|
+ const overarr = [] as any
|
|
|
|
|
+ for (let z = 0; z < newARR.length; z++) {
|
|
|
|
|
+ const item = newARR[z] as any
|
|
|
|
|
+ if (!item) continue
|
|
|
|
|
+ // 汇总
|
|
|
|
|
+ for (let d = 0; d < item.list.length; d++) {
|
|
|
|
|
+ const itd = item.list[d]
|
|
|
|
|
+ overarr.push({
|
|
|
|
|
+ x: Math.floor(itd.x),
|
|
|
|
|
+ y: Math.floor(itd.y)
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return overarr
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取两点间的点
|
|
|
|
|
+ * @param Vector3
|
|
|
|
|
+ * @param num
|
|
|
|
|
+ * @returns
|
|
|
|
|
+ */
|
|
|
|
|
+ getPoints(Vector3: { s: (number | undefined)[]; e: (number | undefined)[]; }, num = 59) {
|
|
|
|
|
+ const curve = new Three.CatmullRomCurve3(
|
|
|
|
|
+ [
|
|
|
|
|
+ new Three.Vector3(Vector3.s[0], Vector3.s[1], Vector3.s[2]),
|
|
|
|
|
+ new Three.Vector3(Vector3.e[0], Vector3.e[1], Vector3.e[2])
|
|
|
|
|
+ ],
|
|
|
|
|
+ false,
|
|
|
|
|
+ 'catmullrom',
|
|
|
|
|
+ 0.5
|
|
|
|
|
+ )
|
|
|
|
|
+ // const points = curve.getPoints(60).reduce((arr, item) => {
|
|
|
|
|
+ // return arr.concat(item.x, item.y, item.z)
|
|
|
|
|
+ // }, [])
|
|
|
|
|
+ const points = curve.getSpacedPoints(num)
|
|
|
|
|
+ return points
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置中心
|
|
|
|
|
+ * @param obj
|
|
|
|
|
+ * @param option
|
|
|
|
|
+ * @returns
|
|
|
|
|
+ */
|
|
|
|
|
+ setObjCenter(obj: Three.Object3D<Three.Event>, option = { x: 0, y: 0, z: 0 }) {
|
|
|
|
|
+ const group = new Three.Object3D()
|
|
|
|
|
+ group.position.set(option.x, option.y, option.z)
|
|
|
|
|
+ obj.position.set(-option.x, -option.y, -option.z)
|
|
|
|
|
+ group.add(obj)
|
|
|
|
|
+ return group
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清楚缓存
|
|
|
|
|
+ */
|
|
|
|
|
+ clearThreeOBJ() {
|
|
|
|
|
+ // 清除所有缓存
|
|
|
|
|
+ this.scene.clear()
|
|
|
|
|
+ this.scene.remove()
|
|
|
|
|
+ this.renderer.dispose()
|
|
|
|
|
+ this.renderer.forceContextLoss();
|
|
|
|
|
+ (this.renderer as any).content = null
|
|
|
|
|
+ this.renderer.domElement?.remove()
|
|
|
|
|
+ this.renderer.domElement = null as any
|
|
|
|
|
+ Three.Cache.clear()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|