zhouenguang 2 سال پیش
کامیت
ccda9a83b4
12فایلهای تغییر یافته به همراه2030 افزوده شده و 0 حذف شده
  1. 1 0
      9099.bat
  2. 157 0
      PanoWireframe.js
  3. 86 0
      Scene3d.js
  4. 153 0
      TextSprite.js
  5. 9 0
      Vectors.js
  6. 204 0
      index.html
  7. 1217 0
      lib/OrbitControls.js
  8. 2 0
      lib/axios.min.js
  9. 1 0
      lib/base64.min.js
  10. 90 0
      lib/bytebuffer.min.js
  11. 108 0
      lib/protobuf.min.js
  12. 2 0
      lib/three.min.js

+ 1 - 0
9099.bat

@@ -0,0 +1 @@
+http-server -p 9099  -P https://test.4dkankan.com

+ 157 - 0
PanoWireframe.js

@@ -0,0 +1,157 @@
+import { TextSprite } from "./TextSprite.js"
+import Vectors from "./Vectors.js"
+
+/**
+ * 根据算法提供的全景图点数据,在3d中标记并围成一个矩形线框
+ */
+
+export default class PanoWireframe extends THREE.Group {
+    constructor(scene, sceneCode) {
+        super()
+        this.sceneCode = sceneCode
+        scene.add(this)
+    }
+
+    clearAll() {
+        // 清除已有线框
+        this.traverse(obj => {
+            if (obj.isMesh) {
+                obj.geometry.dispose()
+                obj.material.dispose()
+            }
+        })
+        this.clear()
+    }
+
+    /**
+     * 加载点位标记数据
+     * @param {*} panoId
+     */
+    async load(panoId) {
+        let data = await axios.post(
+            "/service/scene/sceneMarkShape/getInfo", 
+            {
+                num: this.sceneCode,
+                imagePath: panoId + ".jpg"
+            }
+        )
+        data = data.data
+        if (!data.data || !data.success) return
+
+        let { shapes, imageHeight, imageWidth } = data.data
+
+        let labels = ""
+        shapes.forEach(shape => {
+            // 填充色和线框色
+            // let { fill_color, line_color } = shape
+            let { fill_color, color } = shape
+            let line_color = [...color, 255]
+
+            if (!fill_color) fill_color = [255, 255, 255, 0]
+            if (!line_color) line_color = [255, 0, 0, 255]
+
+            this.showSignalFrom2d(shape.category, shape.bbox, imageWidth, imageHeight, {
+                fill: {
+                    color: new THREE.Color().setRGB(fill_color[0] / 255, fill_color[1] / 255, fill_color[2] / 255),
+                    opacity: fill_color[3] / 255,
+                },
+                line: {
+                    color: new THREE.Color().setRGB(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255),
+                    opacity: line_color[3] / 255,
+                },
+            })
+
+            labels += "<li>" + shape.label + '</li>'
+        })
+
+        return labels
+    }
+
+    /**
+     * 根据坐标标记全景图
+     */
+    showSignalFrom2d(name, rect, w, h, options) {
+        // 目前rect给的是矩形对角的两个点坐标,将它扩展成四个顶点
+        let cornerArr = [
+            [rect[0], rect[1]],
+            [rect[2], rect[1]],
+            [rect[2], rect[3]],
+            [rect[0], rect[3]],
+        ]
+
+        // 2d坐标转3d坐标
+        let transform2dTo3d = point => {
+            // 计算方向向量
+            let x = point[0],
+                y = point[1]
+            let yaw = (-x / w) * (Math.PI * 2)
+            let pitch = Math.PI / 2 - (y / h) * Math.PI
+
+            let dir = new THREE.Vector3()
+            dir.copy(Vectors.RIGHT).applyAxisAngle(Vectors.BACK, pitch).applyAxisAngle(Vectors.UP, yaw)
+
+            return dir
+        }
+
+        // // 计算矩形线框中点坐标(取x、y的平均值)
+        // let center = [cornerArr.reduce((a, b) => [a[0] + b[0], 0])[0] / cornerArr.length, cornerArr.reduce((a, b) => [0, a[1] + b[1]])[1] / cornerArr.length]
+        // let centerVec = transform2dTo3d(center) // 计算中点3d坐标
+
+        // 根据四个顶点,填充中间点
+        let pointArr = []
+        for (let i = 0; i < cornerArr.length; i++) {
+            let corner1 = cornerArr[i]
+            let corner2 = cornerArr[(i + 1) % cornerArr.length]
+            pointArr.push(corner1)
+
+            // 横向角度超过150度时,3d中边框的弧线已经不太明显,准确画出全景图线框
+            if ((rect[2] - rect[0]) / w < 5 / 12 && i % 2 == 0) continue
+
+            const vec = [corner2[0] - corner1[0], corner2[1] - corner1[1]]
+            let length = Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1])
+            let num = length / 150
+
+            for (let j = 1; j <= num; j++) {
+                pointArr.push([corner1[0] + (vec[0] / num) * j, corner1[1] + (vec[1] / num) * j])
+            }
+        }
+
+        let points = []
+        pointArr.forEach(point => {
+            let dir = transform2dTo3d(point)
+            // points.push(dir.sub(centerVec))  // 计算其他点相对于中点的坐标,方便旋转平移等
+            points.push(dir)
+        })
+
+        // 线框
+        const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
+        const lineMaterial = new THREE.LineBasicMaterial({ color: options.line.color, opacity: options.line.opacity, transparent: true, depthTest: false })
+        const wireframe = new THREE.LineLoop(lineGeometry, lineMaterial)
+        // wireframe.position.copy(centerVec)  // 将中点作为线框坐标
+        wireframe.renderOrder = 100
+
+        // 填充颜色
+        const fillGeometry = lineGeometry.clone().setIndex(new THREE.BufferAttribute(new Uint16Array([0, 1, 3, 2, 3, 1]), 1))
+        const fillMaterial = new THREE.MeshBasicMaterial({ color: options.fill.color, opacity: options.fill.opacity, transparent: true, side: THREE.DoubleSide, depthTest: false })
+        const plane = new THREE.Mesh(fillGeometry, fillMaterial)
+        plane.renderOrder = wireframe.renderOrder - 1
+        wireframe.add(plane)
+
+        // 名称
+        const textMesh = new TextSprite({ 
+            text: name,
+            backgroundColor: { r: options.line.color.r * 255, g: options.line.color.g * 255, b: options.line.color.b * 255, a: 0.4 },
+            textColor: { r: 255, g: 255, b: 255, a: 1 },
+            borderRadius: 15,
+            renderOrder: wireframe.renderOrder + 1
+        })
+        textMesh.position.copy(points[0])   // 线框左上角
+        textMesh.lookAt(0, 0, 0)    // 看向相机
+        textMesh.scale.set(0.2, 0.2, 0.2)
+
+        let group = new THREE.Group()
+        group.add(wireframe)
+        group.add(textMesh)
+        this.add(group)
+    }
+}

+ 86 - 0
Scene3d.js

@@ -0,0 +1,86 @@
+import PanoWireframe from './PanoWireframe.js'
+
+var renderer, camera, scene, controls;
+var cameraDefault = new THREE.Vector3(0, 0, 0.001);
+var lookAtPoint = new THREE.Vector3(0, 0, 0);
+
+export default class Scene3d {
+    constructor(options) {
+        this.dom = options.dom
+        this.width = options.width
+        this.height = options.height
+        this.sceneCode = options.sceneCode
+
+        /**
+         * 初始化渲染器
+         */
+        function initRenderer() {
+            renderer = new THREE.WebGLRenderer({antialias: true});
+            renderer.setClearColor(0x333333, 1);
+            renderer.setSize(options.width, options.height);
+            renderer.shadowMap.enabled = true;
+            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+            options.dom.appendChild( renderer.domElement );
+        }
+        initRenderer()
+
+        /**
+         * 初始化场景
+         */
+        function initScene() {
+            scene = new THREE.Scene();
+        }
+        initScene()
+
+        /**
+         * 初始化相机
+         */
+        function initCamera() {
+            camera = new THREE.PerspectiveCamera(90, options.width / options.height, 0.01, 1000);
+            camera.position.set(cameraDefault.x, cameraDefault.y, cameraDefault.z);
+            camera.lookAt(lookAtPoint);
+        }
+        initCamera()
+
+         /**
+         * 初始化控制器
+         */
+        function initControls() {
+            controls = new THREE.OrbitControls( camera, renderer.domElement );
+            controls.rotateSpeed = 2.0;     // 旋转速度
+            controls.zoomSpeed = 1.2;       // 缩放速度
+            controls.panSpeed = 0.8;        // 平controls
+            controls.staticMoving = true;   // 静止移动,为 true 则没有惯性
+            controls.dynamicDampingFactor = 0.3;    // 阻尼系数 越小 则滑动越大
+            controls.saveState()
+        }
+        initControls()
+        
+        
+        const geometry = new THREE.SphereGeometry( 30, 32, 16 );
+        const material = new THREE.MeshBasicMaterial( { side: 2 } );
+        this.sky = new THREE.Mesh( geometry, material );
+        this.sky.scale.x = -1
+        scene.add( this.sky );
+
+        this.panoWireframe = new PanoWireframe(scene, this.sceneCode)
+
+
+        function animate() {
+            renderer.render(scene, camera);
+            controls.update();
+            requestAnimationFrame(animate);
+        }
+        animate();
+    }
+
+    async load(panoId) {
+        let texture = new THREE.TextureLoader().load(`https://4dkk.4dage.com/scene_view_data/${this.sceneCode}/images/pan/high/${panoId}.jpg`)
+        this.sky.material.map = texture
+        this.sky.material.needsUpdate = true
+
+        this.panoWireframe.clearAll()
+        let labels = await this.panoWireframe.load(panoId)
+        return labels
+    }
+}

+ 153 - 0
TextSprite.js

@@ -0,0 +1,153 @@
+export class TextSprite extends THREE.Object3D {
+    constructor(options = {}) {
+        super()
+        let map = new THREE.Texture()
+        map.minFilter = THREE.LinearFilter
+        map.magFilter = THREE.LinearFilter
+
+        this.sprite = new THREE.Sprite(
+            new THREE.SpriteMaterial({
+                map,
+                color: 0xffffff,
+                transparent: true,
+                depthTest: false,
+                depthWrite: false,
+            })
+        )
+        this.add(this.sprite)
+        this.sprite.renderOrder = options.renderOrder != void 0 ? options.renderOrder : 2
+
+        this.rectBorderThick = options.rectBorderThick || 0
+        this.textBorderThick = options.textBorderThick || 0
+        this.fontface = 'Arial'
+        this.fontsize = options.fontsize || 16
+        this.textBorderColor = options.textBorderColor || { r: 0, g: 0, b: 0, a: 0.0 }
+        this.backgroundColor = options.backgroundColor || { r: 255, g: 255, b: 255, a: 1.0 }
+        this.textColor = options.textColor || { r: 0, g: 0, b: 0, a: 1.0 }
+        this.borderColor = options.borderColor || { r: 0, g: 0, b: 0, a: 0.0 }
+        this.borderRadius = options.borderRadius || 6
+        if (options.text != void 0) this.setText(options.text)
+        this.name = options.name
+
+        //this.setText(text);
+
+        this.addEventListener('dispose', this.dispose.bind(this))
+    }
+
+    setText(text) {
+        if (this.text !== text) {
+            this.text = text + ''
+
+            this.updateTexture()
+        }
+    }
+
+    setTextColor(color) {
+        this.textColor = color
+
+        this.updateTexture()
+    }
+
+    setBorderColor(color) {
+        this.borderColor = color
+
+        this.updateTexture()
+    }
+
+    setBackgroundColor(color) {
+        this.backgroundColor = color
+
+        this.updateTexture()
+    }
+    setPos(pos) {
+        this.position.copy(pos)
+        this.sprite.update()
+    }
+    update() {
+        this.sprite.update()
+    }
+    setVisible(v) {
+        this.visible = v
+    }
+    setUniforms(name, value) {
+        this.sprite.setUniforms(name, value)
+    }
+    updateTexture() {
+        let canvas = document.createElement('canvas')
+        let context = canvas.getContext('2d')
+        context.font = 'Bold ' + this.fontsize + 'px ' + this.fontface
+
+        context['font-weight'] = 100 //语法与 CSS font 属性相同。
+        // get size data (height depends only on font size)
+
+        //this.text = '啊啊啊啊啊啊fag'
+
+        let metrics = context.measureText(this.text)
+        let textWidth = metrics.width
+        let margin = new THREE.Vector2(this.fontsize, this.fontsize * 0.4)
+        let spriteWidth = 2 * margin.x + textWidth + 2 * this.rectBorderThick
+        let spriteHeight = 2 * margin.y + this.fontsize + 2 * this.rectBorderThick
+        context.canvas.width = spriteWidth
+        context.canvas.height = spriteHeight
+        context.font = 'Bold ' + this.fontsize + 'px ' + this.fontface
+
+        let diff = 2 //针对英文大部分在baseLine之上所以降低一点(metrics.fontBoundingBoxAscent - metrics.fontBoundingBoxDescent) / 2
+
+        context.textBaseline = 'middle'
+
+        // border color
+        context.strokeStyle = 'rgba(' + this.borderColor.r + ',' + this.borderColor.g + ',' + this.borderColor.b + ',' + this.borderColor.a + ')'
+
+        context.lineWidth = this.rectBorderThick
+        // background color
+        context.fillStyle = 'rgba(' + this.backgroundColor.r + ',' + this.backgroundColor.g + ',' + this.backgroundColor.b + ',' + this.backgroundColor.a + ')'
+        this.roundRect(context, this.rectBorderThick / 2, this.rectBorderThick / 2, spriteWidth - this.rectBorderThick, spriteHeight - this.rectBorderThick, this.borderRadius)
+
+        // text color
+        if (this.textBorderThick) {
+            context.strokeStyle = 'rgba(' + this.textBorderColor.r + ',' + this.textBorderColor.g + ',' + this.textBorderColor.b + ',' + this.textBorderColor.a + ')'
+            context.lineWidth = this.textBorderThick
+            context.strokeText(this.text, this.rectBorderThick + margin.x, spriteHeight / 2 + diff)
+        }
+
+        context.fillStyle = 'rgba(' + this.textColor.r + ',' + this.textColor.g + ',' + this.textColor.b + ',' + this.textColor.a + ')'
+        context.fillText(this.text, this.rectBorderThick + margin.x, spriteHeight / 2 + diff) //x,y
+
+        let texture = new THREE.Texture(canvas)
+        texture.minFilter = THREE.LinearFilter
+        texture.magFilter = THREE.LinearFilter
+        texture.needsUpdate = true
+        //this.material.needsUpdate = true;
+
+        if (this.sprite.material.map) {
+            this.sprite.material.map.dispose()
+        }
+        this.sprite.material.map = texture
+
+        this.sprite.scale.set(spriteWidth * 0.01, spriteHeight * 0.01, 1.0)
+    }
+
+    roundRect(ctx, x, y, w, h, r) {
+        ctx.beginPath()
+        ctx.moveTo(x + r, y)
+        ctx.lineTo(x + w - r, y)
+        ctx.arcTo(x + w, y, x + w, y + r, r) //圆弧。前四个参数同quadraticCurveTo
+        //ctx.quadraticCurveTo(x + w, y, x + w, y + r); //二次贝塞尔曲线需要两个点。第一个点是用于二次贝塞尔计算中的控制点,第二个点是曲线的结束点。
+        ctx.lineTo(x + w, y + h - r)
+        ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
+        ctx.lineTo(x + r, y + h)
+        ctx.arcTo(x, y + h, x, y + h - r, r)
+        ctx.lineTo(x, y + r)
+        ctx.arcTo(x, y, x + r, y, r)
+        ctx.closePath()
+        ctx.fill()
+        ctx.stroke()
+    }
+
+    dispose() {
+        this.sprite.material.uniforms.map.value.dispose()
+        this.parent && this.parent.remove(this)
+        this.sprite.dispatchEvent({ type: 'dispose' })
+        this.removeAllListeners()
+    }
+}

+ 9 - 0
Vectors.js

@@ -0,0 +1,9 @@
+export default {
+    UP: new THREE.Vector3(0, 1, 0),
+    DOWN: new THREE.Vector3(0, -1, 0),
+    LEFT: new THREE.Vector3(-1, 0, 0),
+    RIGHT: new THREE.Vector3(1, 0, 0),
+    FORWARD: new THREE.Vector3(0, 0, -1),
+    BACK: new THREE.Vector3(0, 0, 1),
+    ZERO: new THREE.Vector3(0, 0, 0),
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 204 - 0
index.html


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1217 - 0
lib/OrbitControls.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 0
lib/axios.min.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1 - 0
lib/base64.min.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 90 - 0
lib/bytebuffer.min.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 108 - 0
lib/protobuf.min.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 0
lib/three.min.js