现在随着城市的发展,越来越多的智慧摄像头,都被互联网公司布到城市的各个角落,举一个例子,一个大楼上上下下都被布置了智能摄像头,用于监控火势,人员进出,工装工牌佩戴等监控,这时候为了美化项目,大公司都会将城市的区域作为对象,进行3d可视化交互,接下来的内容,就是基于以上元素,开发的一款城市数据可视化的demo,包含楼宇特效,飞线,特定视角,动画等交互,
用到的技术栈 vite + typescript + threejs
搜索关键词:city
压缩包包含的内容
下面是具体代码
export function loadGltf(url: string) { return new Promise((resolve, reject) => { gltfLoader.load(url, function (gltf) { console.log('gltf',gltf) resolve(gltf) }); }) } 处理模型 模型上有一些咱们用不到的模型,进行删除,还有一些用的到的模型,但是名称不友好,所以进行整理 loadGltf('./models/scene.gltf').then((gltf: any) => { const group = gltf.scene const scale = 10 group.scale.set(scale, scale, scale) // 删除多余模型 const mesh1 = group.getObjectByName('Text_test-base_0') if (mesh1 && mesh1.parent) mesh1.parent.remove(mesh1) const mesh2 = group.getObjectByName('Text_text_0') if (mesh2 && mesh2.parent) mesh2.parent.remove(mesh2) // 重命名模型 // 环球金融中心 const hqjrzx = group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if (hqjrzx) hqjrzx.name = 'hqjrzx' // 上海中心 const shzx = group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if (shzx) shzx.name = 'shzx' // 金茂大厦 const jmds = group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if (jmds) jmds.name = 'jmds' // 东方明珠塔 const dfmzt = group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if (dfmzt) dfmzt.name = 'dfmzt' T.scene.add(group) T.toSceneCenter(group) T.ray(group.children, (meshList) => { console.log('meshList', meshList); }) T.animate() }) T是场景的构建函数,主要创建了场景,镜头,控制器,灯光等基础信息,并且监听控制器变化时修改灯光位置 在使用第三方模型的时候,总有一些不尽人意的地方,比如模型加载后,模型中心并不在3d世界的中心位置,所以就需要调整一下模型整体的位置,toSceneCenter 方法是自定义的一个让模型居中的方法,通过BOX3获取到模型的包围盒,获取到模型的中心点坐标信息,再取反,就会得到模型中心点在3d世界的位置信息 // 获取包围盒 getBoxInfo(mesh) { const box3 = new THREE.Box3() box3.expandByObject(mesh) const size = new THREE.Vector3() const center = new THREE.Vector3() // 获取包围盒的中心点和尺寸 box3.getCenter(center) box3.getSize(size) return { size, center } } toSceneCenter(mesh) { const { center, size } = this.getBoxInfo(mesh) // 将Y轴置为0 mesh.position.copy(center.negate().setY(0)) } 飞线 收集飞线的点 没有3d设计师的支持,所有的数据都来自于模型,所以利用现有条件,收集飞线经过的点位,原理就是使用到的鼠标射线,点击模型上的某个位置并记录下来,提供给后期使用 众所周知,click的调用过程是忽略mousedown的,mouseup时候就会调用,如果单纯的想要改变视角,鼠标抬起时候也会调用click事件,所以要加一个鼠标是否移动的判断,利用控制器监听start和end时的镜头位置变化来区分鼠标是否移动 控制器部分代码: this.controls.addEventListener('start', () => { this.controlsStartPos.copy(this.camera.position) }) this.controls.addEventListener('end', () => { this.controlsMoveFlag = this.controlsStartPos.distanceToSquared(this.camera.position) === 0 }) 控制器开始变化的时候记录camera位置,跟结束时的camera的位置相减,如果为0,则表示鼠标没晃动,单纯的点击,如果不为0,说明镜头位置变化了,这时,鼠标的click回调将不会调用 射线部分代码: ray(children: THREE.Object3D[], callback: (mesh: THREE.Intersection>[]) => void) { let mouse = new THREE.Vector2(); //鼠标位置 var raycaster = new THREE.Raycaster(); window.addEventListener("click", (event) => { mouse.x = (event.clientX / document.body.offsetWidth) * 2 - 1; mouse.y = -(event.clientY / document.body.offsetHeight) * 2 + 1; raycaster.setFromCamera(mouse, this.camera); const rallyist = raycaster.intersectObjects(children); if (this.controlsMoveFlag) { callback && callback(rallyist) } }); } 射线的回调: let arr = [] T.ray(group.children, (meshList) => { console.log('meshList', meshList); arr.push(...meshList[0].point.toArray()) console.log(JSON.stringify(arr)); }) 收集后的顶点信息: 这部分的工作只不过判断鼠标是否移动的部分不一样而已。 细化顶点 有了飞线具体经过的点位时候,要将这些点位细化,这时就要讲飞线的大致原理了,两点确定一条线段,获取线段上的100个点,每条飞线占用20个点位,每个点位创建一个着色器,用于绘制飞线的组成部分,当更新时候,飞线的首个点向下一个点前进,一次往后20个点都往前前进一次,循环往复一直到飞线的最后一个组成部分到达线段的最后一个点,飞线占用的点位数量决定飞线的长度,将线段分为多少个顶点,决定飞线的疏密程度,像图中这样的疏密度,就是单个线段的点位分少了,这个可以优化的,vector3.distanceTo(vector3)即可判断两个线段的长度,通过不同的长度,决定细化线段的点,当然,线段的顶点信息越多,对gpu的消耗越大 flyLineData.forEach((data: number[]) => { const points: THREE.Vector2[] = [] for (let i = 0; i < data.length / 3; i++) { const x = data[i * 3] const z = data[i * 3 + 2] const point = new THREE.Vector2(x, z) points.push(point) } const curve = new THREE.SplineCurve(points); // 此处决定飞线每个点的疏密程度,数值越大,对gpu的压力越大 const curvePoints = curve.getPoints(100); const flyPoints = curvePoints.map((curveP: THREE.Vector2) => new THREE.Vector3(curveP.x, 0, curveP.y)) // const l = points.length - 1 const flyGroup = T._Fly.setFly({ index: Math.random() > 0.5 ? 50 : 20, num: 20, points: flyPoints, spaced: 50, // 要将曲线划分为的分段数。默认是 5 starColor: new THREE.Color(Math.random() * 0xffffff), endColor: new THREE.Color(Math.random() * 0xffffff), size: 0.5 }) flyLineGroup.add(flyGroup) }) setFly参数 interface SetFly { index: number, // 截取起点 num: number, // 截取长度 // 要小于length points: Vector3[], spaced: number // 要将曲线划分为的分段数。默认是 5 starColor: Color, endColor: Color, size: number } endColor和starColor目前不好用,做不出渐变,不知道是不是长度不够,暂时先放放 flyLine 创建flyLine做成了一个类,开箱即用,也可以加入自己的想法,调整内容, 创建flyLine之后要在render中调用 render() { this.controls.update() this.renderer.render(this.scene, this.camera); this._Fly && this._Fly.upDate() } 可配置参数有尺寸,透明度,颜色等 var color1 = params.starColor; //轨迹线颜色 青色 var color2 = params.endColor; //黄色 var color = color1.lerp(color2, i / newPoints2.length) colorArr.push(color.r, color.g, color.b); 这里是引用渐变色的位置,需要再调整一下 阶段代码 线稿 将模型绘制出线稿,并添加到原有模型上,这里用到LineBasicMaterial基础线条材质,和MeshLambertMaterial基础网格材质,调节颜色和不透明度。 材质代码: // 建筑材质 export const otherBuildingMaterial = (color: THREE.Color, opacity = 1) => { return new THREE.MeshLambertMaterial({ color, transparent: true, opacity }); } // 建筑线条材质 export const otherBuildingLineMaterial = (color: THREE.Color, opacity = 1) => { return new THREE.LineBasicMaterial( { color, depthTest: true, transparent: true, opacity } ) } 以下代码是之前对模型改造时写的对模型重命名的方法,现在我们来改造一下 // 重命名模型 // 环球金融中心 const hqjrzx = group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if (hqjrzx) { hqjrzx.name = 'hqjrzx' changeModelMaterial(hqjrzx, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 上海中心 const shzx = group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if (shzx) { shzx.name = 'shzx' changeModelMaterial(shzx, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 金茂大厦 const jmds = group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if (jmds) { jmds.name = 'jmds' changeModelMaterial(jmds, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 东方明珠塔 const dfmzt = group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if (dfmzt) { dfmzt.name = 'dfmzt' changeModelMaterial(dfmzt, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } T.scene.add(group) T.toSceneCenter(group) group.traverse((mesh: any) => { mesh as THREE.Mesh if (mesh.isMesh && (mesh.name.indexOf('Shanghai') !== -1 || mesh.name.indexOf('Object') !== -1)) { if (mesh.name.indexOf('Floor') !== -1) { mesh.material = floorMaterial } else if (mesh.name.indexOf('River') !== -1) { } else { changeModelMaterial(mesh, otherBuildingMaterial(otherBuildColor,0.8), otherBuildingLineMaterial(otherBuildLineColor,0.4),buildLineDeg) } } }) changeModelMaterial这个方法就是创建模型相对应的线条的方法,获取到模型的geometry,这里存着模型所有的顶点信息,索引和法向量,以此创建一个# 边缘几何体(EdgesGeometry)边缘几何体(EdgesGeometry)# 边缘几何体(EdgesGeometry);通过边缘几何体的信息创建 # 线段(LineSegments)线段(LineSegments) # 线段(LineSegments);并将创建出来的线段添加到原有模型中,因为我们的线段不需要单独处理, /** * * @param object 模型 * @param lineGroup 线组 * @param meshMaterial 模型材质 * @param lineMaterial 线材质 */ export const changeModelMaterial = (mesh: THREE.Mesh, meshMaterial: THREE.MeshBasicMaterial, lineMaterial: THREE.LineBasicMaterial, deg = 1): any => { if (mesh.isMesh) { if (meshMaterial) mesh.material = meshMaterial // 以模型顶点信息创建线条 const line = getLine(mesh, deg, lineMaterial) const name = mesh.name + '_line' line.name = name mesh.add(line) } } // 通过模型创建线条 export const getLine = (object: THREE.Mesh, thresholdAngle = 1, lineMaterial: THREE.LineBasicMaterial): THREE.LineSegments => { // 创建线条,参数为 几何体模型,相邻面的法线之间的角度, var edges = new THREE.EdgesGeometry(object.geometry, thresholdAngle); var line = new THREE.LineSegments(edges); if (lineMaterial) line.material = lineMaterial return line; } 关于颜色 对于我这种野生前端开发,没有UI和UE的支持,只能在网上找案例,那么就需要图片中的颜色,这里不得不提到一个工具色輪、調色盤產生器 | Adobe Color 色彩 这里可以根据一个颜色,调出互补色、相似色、单色等色彩信息 取色 这个工具也可以根据一张图片,提取出主题色,包含主色、辅助色等信息 阶段代码 预设镜头位置 预埋点位 预埋的点位坐标信息获取和飞线点位获取一样的方法,标记采用的是CSS2DRenderer,将创建的element节点渲染到3d世界,3drender和2drender不在同一个图层内,所以需要新建一个dom节点,专门存放css2d的dom信息, 加载css2drender createScene 文件 +renderCss2D: CSS2DRenderer createRenderer(){ ... this.renderCss2D = new CSS2DRenderer({ element: this.css2dDom }); this.renderCss2D.setSize(this.width, this.height); ... } render(){ ... this.renderCss2D.render(this.scene, this.camera); ... } 根据数据创建dom节点 export interface CameraPosInfo { pos: number[], // 预设摄像机位置信息 target: number[], // 控制器目标位置 name: string, // 预埋标记点或其他信息 tagPos?: number[], // 预埋标记点的位置信息 } 接下来就是要根据信息创建节点,遍历这些信息,并创建节点,这里有一个点需要提一下,2d图层和3d图层的关系 从图中可以看出,2d图层始终保持在3d图层的上层,然而我们在创建控制器的时候,第二个参数使用的是3d的图层, this.controls = new OrbitControls(this.camera, this.renderer.domElement),因为这一层被覆盖了,所以控制器失效了。 有两种解决方案,第一种是 new OrbitControls时,将第二个参数改为this.renderCss2D.domElement,还有一种方式,也就是本文采用的方式,将2d图层的css属性改变一下,忽略这个图层的任何事件。 #css2dRender { /* 一定要加这个属性,不然2D内容点击没效果 */ pointer-events: none; } 由于pointer-events属性是可以继承的,2d图层内所有的元素都不响应事件,所以要将咱们创建的建筑tag的样式改一下 .build_tag { /* 一定要加这个属性,不然2D内容点击没效果 */ pointer-events: all; } // 创建建筑标记 function createTag() { const buildTagGroup = new THREE.Group() T.scene.add(buildTagGroup) presetsCameraPos.forEach((cameraPos: CameraPosInfo, i: number) => { if (cameraPos.tagPos) { // 渲染2d文字 const element = document.createElement('li'); // 将信息存入dom节点中,如果是react或者vue写的,不用这么存,直接存data或者state element.setAttribute('data-cameraPosInfo', JSON.stringify(cameraPos)) element.classList.add('build_tag') element.innerText = `${i + 1}` // 将初始化好的dom节点渲染成CSS2DObject,并在scene场景中渲染 const tag = new CSS2DObject(element); const tagPos = new THREE.Vector3().fromArray(cameraPos.tagPos) tag.position.copy(tagPos) buildTagGroup.add(tag) } }) } 镜头动画 这里通过事件代理,点击到相应的建筑tag,从dom节点上获取到data-cameraPosInfo属性,然后通过tween动画处理器修改控制器的taget和镜头的position。事件代理是js基础内容, if (css2dDom) { css2dDom.addEventListener('click', function (e) { if (e.target) { if(e.target.nodeName=== 'LI') { console.dir(e); const cameraPosInfo = e.target.getAttribute('data-cameraPosInfo') if (cameraPosInfo) { const {pos,target} = JSON.parse(cameraPosInfo) T.controls.target.set(...target) T.handleCameraPos(pos) } } } }); } handleCameraPos的代码 handleCameraPos(end: number[]) { // 结束时候相机位置 const endV3 = new THREE.Vector3().fromArray(end) // 目前相机到目标位置的距离,根据不同的位置判断运动的时间长度,从而保证速度不变 const length = this.camera.position.distanceTo(endV3) // 如果位置相同,不运行动画 if(length===0) return new this._TWEEN.Tween(this.camera.position) .to(endV3, Math.sqrt(length) * 400) .start() // .onUpdate((value) => { // console.log(value) // }) .onComplete(() => { // 动画结束的回调,可以展示建筑信息或其他操作 }) } 阶段代码 场景背景渲染 scene的场景不仅支持颜色和texture纹理,还支持canvas,上面的黑色背景太单调了,所以利用canvas绘制一个圆渐变填充到scene.background createScene(){ ... const drawingCanvas = document.createElement('canvas'); const context = drawingCanvas.getContext('2d'); if (context) { // 设置canvas的尺寸 drawingCanvas.width = this.width; drawingCanvas.height = this.height; // 创建渐变 const gradient = context.createRadialGradient(this.width / 2, this.height, 0, this.width/2, this.height/2, Math.max(this.width, this.height)); // 为渐变添加颜色 gradient.addColorStop(0, '#0b171f'); gradient.addColorStop(0.6, '#000000'); // 使用渐变填充矩形 context.fillStyle = gradient; context.fillRect(0, 0, drawingCanvas.width, drawingCanvas.height); this.scene.background = new THREE.CanvasTexture(drawingCanvas) ... } 其他风格 你可能感兴趣的:(前端,数据可视化) AI 赋能前端开发:ScriptEcho 如何结合低代码/无代码平台引领未来 wangtaohappy 人工智能低代码前端 1.引言前端开发作为用户与应用交互的桥梁,一直面临着效率、复杂性和快速迭代的挑战。随着互联网技术的飞速发展,用户对应用的用户体验要求越来越高,前端开发的任务也变得更加繁重和复杂。与此同时,低代码/无代码平台的兴起为前端开发带来了新的可能性,它们通过可视化界面和预构建组件,大大降低了开发门槛,加速了开发流程。而AI写代码工具的出现,则进一步赋能了前端开发,为解决传统前端开发模式的痛点提供了新的思路。 AI写代码工具赋能前端开发:效率提升与身心健康 hzcaituowj 人工智能前端 在飞速发展的互联网时代,AI前端开发成为炙手可热的领域。然而,高强度的工作、紧迫的交付时间以及技术日新月异的更新迭代,也给开发者带来了巨大的压力,甚至严重影响着他们的身心健康。长时间伏案工作导致的颈椎病、眼疲劳、精神压力大等问题日益突出。本文将探讨如何利用先进的AI写代码工具提升工作效率,从而有效改善AI前端开发人员的身心健康状况。AI前端开发与身心健康:挑战与应对AI前端开发,特别是涉及到复杂交 广州游戏公司4399秋季招聘火热进行中可查询流程 huaxinjiayou java 新凯来入职体检被卡取消录用面试多起来了byd是真抽象啊,没笔没面直接录取了金蝶第二批组内直招前端开发实习生-最快可当天约面比亚迪两院不卡本2秋招暂时结束,学会和自己和解应届生基本都沦为各公司的免税工具了秋招不要焦虑,机会就在某一瞬间同程一面面试多起来了同程旅行java开发一面凉经byd是真抽象啊,没笔没面直接录取了同程旅行同程旅行一面数字马力(郑州)10/12面经同程一面写面经攒人品-数字马力后端 NVM管理多个 Nodejs 版本 牧云流 前端node.jsnvm 在前端开发过程中,常常需要切换不同版本的NodeJS运行环境,NVM(NodeVersionManager)是一个用于管理多个Node.js版本的工具。卸载以前的Nodejs,并删除node的安装目录安装nvm下载地址安装到D:\Programs\nvm,选择node安装路径D:\Programs\nodejsnvm-v安装指定的版本的nodejsnvminstall版本号nvminstall10 ScriptEcho:AI赋能的前端代码生成神器 begei 人工智能前端 ScriptEcho:AI赋能的前端代码生成神器在前端开发中,如果你总是觉得写代码太费时费力,那么ScriptEcho将成为你的救星。这个AI代码生成平台不仅能帮你省下大量时间,还能让你轻松愉快地写出生产级代码。本文将带你了解ScriptEcho的基本信息、特点,以及如何快速上手。软件简介ScriptEcho是一个专为前端开发者设计的AI代码生成平台,支持Vue、React、uniapp和Flut AI写代码工具赋能前端开发:提升开发者解决问题能力 bd_ming 人工智能前端 近年来,人工智能(AI)技术在各个领域都取得了显著进展,前端开发领域也不例外。AI的快速发展为前端开发者带来了前所未有的机遇,同时也带来了新的挑战。开发者需要不断学习新的技术和工具,以适应快速变化的开发环境。而AI写代码工具的出现,为开发者提升解决问题的能力提供了强有力的支持。本文将探讨AI前端开发工具如何帮助开发者更高效地解决问题,并以ScriptEcho为例进行说明。……传统的Web前端开发工 AI写代码工具赋能前端开发:ScriptEcho 如何激发创新? 2501_90335205 人工智能前端 近年来,人工智能技术飞速发展,深刻地改变着各个行业,前端开发领域也不例外。借助AI写代码工具,开发者们能够以前所未有的速度和效率构建复杂的应用程序,从而释放出更多的时间和精力专注于创新。本文将以ScriptEcho为例,深入探讨AI如何赋能前端开发,提升创新能力。……AI赋能前端创新:效率与创意的平衡传统的前端开发流程往往充满了重复性的工作,例如编写大量的样板代码、处理复杂的布局以及调试各种兼容性 Vue框架介绍 2301_80148369 vue.js前端javascript 一、Vue.js的定义Vue.js是一个开源的前端框架,由尤雨溪(EvanYou)于2014年首次发布。它专注于构建用户界面,允许开发者通过声明式的数据绑定和组件化开发模式来构建复杂的单页面应用(SPA)。Vue.js的设计目标是易于上手和灵活集成,既可以作为小型项目的轻量级库使用,也可以扩展为大型项目的完整框架。二、Vue.js的核心特性响应式数据绑定Vue.js通过响应式系统将数据和视图连接起 第五章:工程化实践 - 第一节 - Tailwind CSS 与前端框架的集成 qianmoQ TailwindCSS:现代化开发实战指南css前端框架前端 TailwindCSS可以与各种现代前端框架完美配合。本节将详细介绍如何将TailwindCSS集成到React、Vue和Angular等主流框架中,并介绍相关的最佳实践。React集成基础配置#创建React项目npxcreate-react-appmy-app--templatetypescript#安装TailwindCSSnpminstall-Dtailwindcsspostcssauto 6款Vue后台管理系统模板 前端 收录一些开箱即用、使用简单、界面美观、功能强大的前端框架,帮助我们后端程序员快速提高学习、工作开发效率(注意:排名不分先后,都是十分优秀的开源前端框架和项目)。收录地址:https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2FYSG...vue-element-adminvue-element-admin是一个后台前端解决方案,它基于vu 前端主流构建工具 本文首发博客网站,由于图片和格式解析问题,可前往阅读原文随着前端开发复杂度的提升,构建工具在开发流程中扮演着越来越重要的角色。这些工具可以帮助开发者实现代码的打包、优化、转译、模块化处理等功能,从而提升开发效率和用户体验。本文将以当前主流的前端构建工具为核心,介绍它们的特点、适用场景及差异构建工具GruntGrunt是较早期的任务运行工具,主要通过任务(tasks)系统自动化完成前端开发的各种操作 CORS跨域问题及解决方案详解 前端跨域cors 大家好,我是V哥,跨域问题是应用开发中比较常见的问题,比如前端应用和后端业务的端口不同,前端要向后端发送请求来获取数据,这个时候就会产生跨域问题,V哥先从跨域问题的产生开始,来详细介绍跨域问题及解决方案。CORS跨域问题的产生原因CORS(Cross-OriginResourceSharing,跨域资源共享)跨域问题源于浏览器的同源策略。同源策略是浏览器的一种安全机制,它要求浏览器在访问一个资源时 Vue 开发效率翻倍:10 个 VS Code 插件助你成为编码高手! HelloZheQ vue.js前端javascript Vue.js作为前端开发的热门框架,其简洁的语法和强大的功能深受开发者喜爱。为了进一步提升Vue开发效率,VSCode提供了丰富的插件生态系统。今天,我就来分享10个我常用的VSCodeVue插件,它们能帮你告别重复劳动,专注于业务逻辑,让你成为真正的Vue编码高手!工欲善其事,必先利其器。选择合适的插件,能让你的Vue开发之旅事半功倍!核心插件:打造Vue开发的基石Vetur(byPineWu) 使用Node.js+Mysql鸿蒙实现个人信息持久化 977.. 鸿蒙node.jsmysql数据库 前端(鸿蒙应用):通过HTTP请求与后端交互。后端(Node.js):提供RESTfulAPI,处理业务逻辑并与MySQL数据库交互。数据库(MySQL):存储用户信息等数据。目录1.后端(Node.js+MySQL)1.1环境准备1.2创建Node.js项目2.前端(鸿蒙应用)2.1创建HTTP工具类2.2调用后端API3.运行流程1.后端(Node.js+MySQL)1.1环境准备安装Node 每日一道面试题(技术随机)什么是前端工程化?它有什么意义? 晚夜微雨问海棠呀 前端 前端工程化是一种系统化、规范化的前端开发方法论,通过整合工具链、制定标准流程和实施最佳实践,将软件工程思想应用于前端开发领域。其核心目标是提升开发效率、保障代码质量和增强可维护性。核心要素:模块化架构采用ESModules/CommonJS实现代码拆分组件化开发(如React/Vue组件体系)//ESModule示例import{utils}from'./core-module';exportde Postman接口测试工具详解【保姆级教程】 CodeQi技术小栈 前端测试工具postman前端开源软件 大家好,我是CodeQi!在我们日常的开发工作中,无论是前端还是后端,API接口的测试都是必不可少的一环。你有没有遇到过这样的情况:接口测试工具复杂难用,使用起来让人抓狂;或者手动构造请求效率低下,容易出错?别担心,我今天要介绍的Postman工具,将会彻底改变你的接口测试体验!Postman是一款功能强大的API开发工具,能够帮助我们轻松地进行API测试、调试和文档生成。通过这篇【保姆级教程】, Vue 3 + Vite 项目中配置代理解决开发环境中跨域请求问题 匹马夕阳 VUE技术集锦vue.js前端javascript 在Vue3+Vite项目中,配置代理是解决开发环境中跨域请求问题的常见方法。通过在Vite的配置文件中设置代理,可以将前端请求转发到后端服务器,从而避免浏览器的同源策略限制。1.创建Vue3+Vite项目首先,确保你已经安装了Node.js。然后,使用以下命令创建一个新的Vue3+Vite项目:npmcreatevite@latestmy-vue-app--templatevue进入项目目录并安装 Beego脱坑(十八)静态文件处理 Clown95 gobeego title:Beego脱坑(十八)静态文件处理tags:go,beego,ormauthor:Clown95刚接触beego的小伙伴,不知道有没有遇到这样的问题,在beego中使用的静态文件,并且路径设置的都正确,但是运行的时候,就是不能加载出来。就说我自己在刚开始使用beego的时候,想要使用layui前端框架,但是运行时layui并没有被调用,改来改出,才发现layui被我放错目录了,我没有放 企业知识库重塑协作生态与数据价值转化 Baklib-企业帮助文档 其他 内容概要在数字化转型浪潮中,企业知识库作为组织智慧资产的核心载体,其价值已从传统的文档存储演变为驱动协作与创新的智能中枢。以Baklib为代表的平台,通过构建智能中台架构,将分散于各部门的非结构化数据转化为可复用资产,实现知识资源的全生命周期管理。该工具本质上属于数字体验平台(DXP)的范畴,其核心功能涵盖知识沉淀、权限分级、跨系统集成及数据可视化,支持企业建立从内容生产到价值转化的完整闭环。专家 前端开发书籍分享 returnShitBoy 前端javascriptcsshtml 为了方便前端开发者搜寻书籍,这期间一直在持续搜集各种前端书籍的电子版,从最初的几十本到后来的两百本,截至今天,一共搜集了330本前端书籍。在这里统一作一个整理,分享给前端开发者。电子书下载地址:链接:https://pan.baidu.com/s/1hTH5NpNhEy0hL0GMtYhLuQ?pwd=zhfi提取码11:zhfi百度网盘链接经常失效,如果失效了请加这个百度网盘群(72566927 使用 Ahooks 解决 React 的闭包问题 ZdqDeveloper react.jsjavascript前端 React是一种流行的前端框架,但在使用React进行开发时,经常会遇到闭包问题。闭包问题主要出现在使用Hooks的函数组件中,由于JavaScript语言特性,函数组件内部的闭包会导致一些意外的行为和错误。为了解决这个问题,可以使用Ahooks库提供的解决方案。本文将详细介绍Ahooks是如何解决React的闭包问题的,并提供相应的源代码示例。首先,我们需要了解闭包问题是如何在React中产生的 鸿蒙5.0实战案例:基于webView的嵌套滚动 敢嗣先锋 鸿蒙开发移动开发HarmonyOSharmonyos鸿蒙开发移动开发ArkUI组件化WebViewWeb组件 往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)✏️鸿蒙(HarmonyOS)北向开发知识点记录~✏️鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~✏️鸿蒙应用开发与鸿蒙系统开发哪个更有前景?✏️嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~✏️对于大前端开发来说,转鸿蒙开发究竟是福还是祸?✏️鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?✏️记录一场鸿蒙开发岗位面 鸿蒙5.0实战案例:基于原生的水印添加能力 敢嗣先锋 移动开发鸿蒙开发HarmonyOSharmonyosc++鸿蒙开发ArkUI移动开发 往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)✏️鸿蒙(HarmonyOS)北向开发知识点记录~✏️鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~✏️鸿蒙应用开发与鸿蒙系统开发哪个更有前景?✏️嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~✏️对于大前端开发来说,转鸿蒙开发究竟是福还是祸?✏️鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?✏️记录一场鸿蒙开发岗位面 web前端--html 5---qq注册 粉0321 web前端前端html qq注册.theme{width:300px;height:600px;margin:0pxauto;}.theme1select{padding:5px10px;border:none;border-color:#B8B8B8;box-sizing:border-box;font-size:14px;color:#B8B8B8;}.a1{color:#4b7cde;}.form-group1in 鸿蒙5.0实战案例:基于原生能力的深色模式适配 敢嗣先锋 鸿蒙开发移动开发HarmonyOSharmonyos组件化移动开发uiArkUI鸿蒙开发 往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)✏️鸿蒙(HarmonyOS)北向开发知识点记录~✏️鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~✏️鸿蒙应用开发与鸿蒙系统开发哪个更有前景?✏️嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~✏️对于大前端开发来说,转鸿蒙开发究竟是福还是祸?✏️鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?✏️记录一场鸿蒙开发岗位面 您的时钟快了-解决方法之一记录 傲娇的小小云 chrome 最近做了个前端项目,自己推到服务期上,自己访问没有问题,但是测试同学一访问就会报这种错。:百度了半天,也排查了很久。在确认程序没有问题后最终发现了问题解决方法之一1。其实就是chrome浏览器版本过低,升级一下版本就可以了。(⚫︎ー⚫︎)balalala~网上还有其他方法,我也没试给大家贴几个可以参考的链接:浏览器无法打开网页,提示「您的时钟快了」该如何解决?-知乎(zhihu.com)(1条消息 【QT教程】QT6硬件高级编程实战案例 QT硬件高级编程 QT性能优化QT原理源码QT界面美化 qtqt6.3qt5c++QT教程 QT6硬件高级编程实战案例使用AI技术辅助生成QT界面美化视频课程QT性能优化视频课程QT原理与源码分析视频课程QTQMLC++扩展开发视频课程免费QT视频课程您可以看免费1000+个QT技术视频免费QT视频课程QT统计图和QT数据可视化视频免费看免费QT视频课程QT性能优化视频免费看免费QT视频课程QT界面美化视频免费看1QT6硬件连接与配置1.1硬件平台选择与搭建1.1.1硬件平台选择与搭建硬 云计算中的API网关是什么?为什么它很重要? 云上的阿七 云计算 在云计算架构中,API网关(APIGateway)是一个重要的组件,主要用于管理、保护和优化不同服务之间的接口(API)通信。简单来说,API网关就像是一个中介,它充当客户端和后端服务之间的“桥梁”,帮助路由、管理、监控、限流以及安全控制所有进出服务的API请求。API网关的工作原理API网关位于应用架构的前端,通常处理以下几项工作:路由请求:根据客户端发起的请求,API网关将其转发到适当的后端服 边缘计算AI盒子目前支持的AI智能算法、视频智能分析算法有哪些,应用于大型厂矿安全生产风险管控 程序员负总裁 人工智能边缘计算安全 一、前端设备实现AI算法主要是基于安卓的布控球实现,已有的算法包括:1)人脸;2)车牌;3)是否佩戴安全帽;4)是否穿着工装;可以支持定制开发烟雾,火焰等智能识别算法。双T卡,双屏显示,安卓系统AI智能布控球,内置人脸、车牌、安全帽识别、烟火识别、抽烟识别等多种AI识别算法,全方位保障工地安全,https://www.besovideo.com/detail?t=2&i=1076AIoT万物智联, 2024年Web前端最新vue动态路由缓存【前进刷新、后退缓存】,2024历年华为跳动前端面试真题解析 2401_84418948 程序员前端面试学习 总结大厂面试问深度,小厂面试问广度,如果有同学想进大厂深造一定要有一个方向精通的惊艳到面试官,还要平时遇到问题后思考一下问题的本质,找方法解决是一个方面,看到问题本质是另一个方面。还有大家一定要有目标,我在很久之前就想着以后一定要去大厂,然后默默努力,每天看一些大佬们的文章,总是觉得只有再学深入一点才有机会,所以才有恒心一直学下去。开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最 java封装继承多态等 麦田的设计者 javaeclipsejvmcencapsulatopn 最近一段时间看了很多的视频却忘记总结了,现在只能想到什么写什么了,希望能起到一个回忆巩固的作用。 1、final关键字 译为:最终的 & F5与集群的区别 bijian1013 weblogic集群F5 http请求配置不是通过集群,而是F5;集群是weblogic容器的,如果是ejb接口是通过集群。 F5同集群的差别,主要还是会话复制的问题,F5一把是分发http请求用的,因为http都是无状态的服务,无需关注会话问题,类似 LeetCode[Math] - #7 Reverse Integer Cwind java题解MathLeetCodeAlgorithm 原题链接:#7 Reverse Integer 要求: 按位反转输入的数字 例1: 输入 x = 123, 返回 321 例2: 输入 x = -123, 返回 -321 难度:简单 分析: 对于一般情况,首先保存输入数字的符号,然后每次取输入的末位(x%10)作为输出的高位(result = result*10 + x%10)即可。但 BufferedOutputStream 周凡杨 首先说一下这个大批量,是指有上千万的数据量。 例子: 有一张短信历史表,其数据有上千万条数据,要进行数据备份到文本文件,就是执行如下SQL然后将结果集写入到文件中! select t.msisd linux下模拟按键输入和鼠标 被触发 linux 查看/dev/input/eventX是什么类型的事件, cat /proc/bus/input/devices 设备有着自己特殊的按键键码,我需要将一些标准的按键,比如0-9,X-Z等模拟成标准按键,比如KEY_0,KEY-Z等,所以需要用到按键 模拟,具体方法就是操作/dev/input/event1文件,向它写入个input_event结构体就可以模拟按键的输入了。 linux/in ContentProvider初体验 肆无忌惮_ ContentProvider ContentProvider在安卓开发中非常重要。与Activity,Service,BroadcastReceiver并称安卓组件四大天王。 在android中的作用是用来对外共享数据。因为安卓程序的数据库文件存放在data/data/packagename里面,这里面的文件默认都是私有的,别的程序无法访问。 如果QQ游戏想访问手机QQ的帐号信息一键登录,那么就需要使用内容提供者COnte 关于Spring MVC项目(maven)中通过fileupload上传文件 843977358 mybatisspring mvc修改头像上传文件upload Spring MVC 中通过fileupload上传文件,其中项目使用maven管理。 1.上传文件首先需要的是导入相关支持jar包:commons-fileupload.jar,commons-io.jar 因为我是用的maven管理项目,所以要在pom文件中配置(每个人的jar包位置根据实际情况定) <!-- 文件上传 start by zhangyd-c --&g 使用svnkit api,纯java操作svn,实现svn提交,更新等操作 aigo svnkit 原文:http://blog.csdn.net/hardwin/article/details/7963318 import java.io.File; import org.apache.log4j.Logger; import org.tmatesoft.svn.core.SVNCommitInfo; import org.tmateso 对比浏览器,casperjs,httpclient的Header信息 alleni123 爬虫crawlerheader @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String type=req.getParameter("type"); Enumeration es=re java.io操作 DataInputStream和DataOutputStream基本数据流 百合不是茶 java流 1,java中如果不保存整个对象,只保存类中的属性,那么我们可以使用本篇文章中的方法,如果要保存整个对象 先将类实例化 后面的文章将详细写到 2,DataInputStream 是java.io包中一个数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。 车辆保险理赔案例 bijian1013 车险 理赔案例: 一货运车,运输公司为车辆购买了机动车商业险和交强险,也买了安全生产责任险,运输一车烟花爆竹,在行驶途中发生爆炸,出现车毁、货损、司机亡、炸死一路人、炸毁一间民宅等惨剧,针对这几种情况,该如何赔付。 赔付建议和方案: 客户所买交强险在这里不起作用,因为交强险的赔付前提是:“机动车发生道路交通意外事故”; 如果是交通意外事故引发的爆炸,则优先适用交强险条款进行赔付,不足的部分由商业 学习Spring必学的Java基础知识(5)—注解 bijian1013 javaspring 文章来源:http://www.iteye.com/topic/1123823,整理在我的博客有两个目的:一个是原文确实很不错,通俗易懂,督促自已将博主的这一系列关于Spring文章都学完;另一个原因是为免原文被博主删除,在此记录,方便以后查找阅读。 有必要对 【Struts2一】Struts2 Hello World bit1129 Hello world Struts2 Hello World应用的基本步骤 创建Struts2的Hello World应用,包括如下几步: 1.配置web.xml 2.创建Action 3.创建struts.xml,配置Action 4.启动web server,通过浏览器访问 配置web.xml <?xml version="1.0" encoding=" 【Avro二】Avro RPC框架 bit1129 rpc 1. Avro RPC简介 1.1. RPC RPC逻辑上分为二层,一是传输层,负责网络通信;二是协议层,将数据按照一定协议格式打包和解包 从序列化方式来看,Apache Thrift 和Google的Protocol Buffers和Avro应该是属于同一个级别的框架,都能跨语言,性能优秀,数据精简,但是Avro的动态模式(不用生成代码,而且性能很好)这个特点让人非常喜欢,比较适合R lua set get cookie ronin47 lua cookie lua: local access_token = ngx.var.cookie_SGAccessToken if access_token then ngx.header["Set-Cookie"] = "SGAccessToken="..access_token.."; path=/;Max-Age=3000" end java-打印不大于N的质数 bylijinnan java public class PrimeNumber { /** * 寻找不大于N的质数 */ public static void main(String[] args) { int n=100; PrimeNumber pn=new PrimeNumber(); pn.printPrimeNumber(n); System.out.print Spring源码学习-PropertyPlaceholderHelper bylijinnan javaspring 今天在看Spring 3.0.0.RELEASE的源码,发现PropertyPlaceholderHelper的一个bug 当时觉得奇怪,上网一搜,果然是个bug,不过早就有人发现了,且已经修复: 详见: http://forum.spring.io/forum/spring-projects/container/88107-propertyplaceholderhelper-bug [逻辑与拓扑]布尔逻辑与拓扑结构的结合会产生什么? comsci 拓扑 如果我们已经在一个工作流的节点中嵌入了可以进行逻辑推理的代码,那么成百上千个这样的节点如果组成一个拓扑网络,而这个网络是可以自动遍历的,非线性的拓扑计算模型和节点内部的布尔逻辑处理的结合,会产生什么样的结果呢? 是否可以形成一种新的模糊语言识别和处理模型呢? 大家有兴趣可以试试,用软件搞这些有个好处,就是花钱比较少,就算不成 ITEYE 都换百度推广了 cuisuqiang GoogleAdSense百度推广广告外快 以前ITEYE的广告都是谷歌的Google AdSense,现在都换成百度推广了。 为什么个人博客设置里面还是Google AdSense呢? 都知道Google AdSense不好申请,这在ITEYE上也不是讨论了一两天了,强烈建议ITEYE换掉Google AdSense。至少,用一个好申请的吧。 什么时候能从ITEYE上来点外快,哪怕少点 新浪微博技术架构分析 dalan_123 新浪微博架构 新浪微博在短短一年时间内从零发展到五千万用户,我们的基层架构也发展了几个版本。第一版就是是非常快的,我们可以非常快的实现我们的模块。我们看一下技术特点,微博这个产品从架构上来分析,它需要解决的是发表和订阅的问题。我们第一版采用的是推的消息模式,假如说我们一个明星用户他有10万个粉丝,那就是说用户发表一条微博的时候,我们把这个微博消息攒成10万份,这样就是很简单了,第一版的架构实际上就是这两行字。第 玩转ARP攻击 dcj3sjt126com r 我写这片文章只是想让你明白深刻理解某一协议的好处。高手免看。如果有人利用这片文章所做的一切事情,盖不负责。 网上关于ARP的资料已经很多了,就不用我都说了。 用某一位高手的话来说,“我们能做的事情很多,唯一受限制的是我们的创造力和想象力”。 ARP也是如此。 以下讨论的机子有 一个要攻击的机子:10.5.4.178 硬件地址:52:54:4C:98 PHP编码规范 dcj3sjt126com 编码规范 一、文件格式 1. 对于只含有 php 代码的文件,我们将在文件结尾处忽略掉 "?>" 。这是为了防止多余的空格或者其它字符影响到代码。例如:<?php$foo = 'foo';2. 缩进应该能够反映出代码的逻辑结果,尽量使用四个空格,禁止使用制表符TAB,因为这样能够保证有跨客户端编程器软件的灵活性。例 linux 脱机管理(nohup) eksliang linux nohupnohup 脱机管理 nohup 转载请出自出处:http://eksliang.iteye.com/blog/2166699 nohup可以让你在脱机或者注销系统后,还能够让工作继续进行。他的语法如下 nohup [命令与参数] --在终端机前台工作 nohup [命令与参数] & --在终端机后台工作 但是这个命令需要注意的是,nohup并不支持bash的内置命令,所 BusinessObjects Enterprise Java SDK greemranqq javaBOSAPCrystal Reports 最近项目用到oracle_ADF 从SAP/BO 上调用 水晶报表,资料比较少,我做一个简单的分享,给和我一样的新手 提供更多的便利。 首先,我是尝试用JAVA JSP 去访问的。 官方API:http://devlibrary.businessobjects.com/BusinessObjectsxi/en/en/BOE_SDK/boesdk_ja 系统负载剧变下的管控策略 iamzhongyong 高并发 假如目前的系统有100台机器,能够支撑每天1亿的点击量(这个就简单比喻一下),然后系统流量剧变了要,我如何应对,系统有那些策略可以处理,这里总结了一下之前的一些做法。 1、水平扩展 这个最容易理解,加机器,这样的话对于系统刚刚开始的伸缩性设计要求比较高,能够非常灵活的添加机器,来应对流量的变化。 2、系统分组 假如系统服务的业务不同,有优先级高的,有优先级低的,那就让不同的业务调用提前分组 BitTorrent DHT 协议中文翻译 justjavac bit 前言 做了一个磁力链接和BT种子的搜索引擎 {Magnet & Torrent},因此把 DHT 协议重新看了一遍。 BEP: 5Title: DHT ProtocolVersion: 3dec52cb3ae103ce22358e3894b31cad47a6f22bLast-Modified: Tue Apr 2 16:51:45 2013 -070 Ubuntu下Java环境的搭建 macroli java工作ubuntu 配置命令: $sudo apt-get install ubuntu-restricted-extras 再运行如下命令: $sudo apt-get install sun-java6-jdk 待安装完毕后选择默认Java. $sudo update- alternatives --config java 安装过程提示选择,输入“2”即可,然后按回车键确定。 js字符串转日期(兼容IE所有版本) qiaolevip TODateStringIE /** * 字符串转时间(yyyy-MM-dd HH:mm:ss) * result (分钟) */ stringToDate : function(fDate){ var fullDate = fDate.split(" ")[0].split("-"); var fullTime = fDate.split(" 【数据挖掘学习】关联规则算法Apriori的学习与SQL简单实现购物篮分析 superlxw1234 sql数据挖掘关联规则 关联规则挖掘用于寻找给定数据集中项之间的有趣的关联或相关关系。 关联规则揭示了数据项间的未知的依赖关系,根据所挖掘的关联关系,可以从一个数据对象的信息来推断另一个数据对象的信息。 例如购物篮分析。牛奶 ⇒ 面包 [支持度:3%,置信度:40%] 支持度3%:意味3%顾客同时购买牛奶和面包。 置信度40%:意味购买牛奶的顾客40%也购买面包。 规则的支持度和置信度是两个规则兴 Spring 5.0 的系统需求,期待你的反馈 wiselyman spring Spring 5.0将在2016年发布。Spring5.0将支持JDK 9。 Spring 5.0的特性计划还在工作中,请保持关注,所以作者希望从使用者得到关于Spring 5.0系统需求方面的反馈。 按字母分类: ABCDEFGHIJKLMNOPQRSTUVWXYZ其他
模型上有一些咱们用不到的模型,进行删除,还有一些用的到的模型,但是名称不友好,所以进行整理
loadGltf('./models/scene.gltf').then((gltf: any) => { const group = gltf.scene const scale = 10 group.scale.set(scale, scale, scale) // 删除多余模型 const mesh1 = group.getObjectByName('Text_test-base_0') if (mesh1 && mesh1.parent) mesh1.parent.remove(mesh1) const mesh2 = group.getObjectByName('Text_text_0') if (mesh2 && mesh2.parent) mesh2.parent.remove(mesh2) // 重命名模型 // 环球金融中心 const hqjrzx = group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if (hqjrzx) hqjrzx.name = 'hqjrzx' // 上海中心 const shzx = group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if (shzx) shzx.name = 'shzx' // 金茂大厦 const jmds = group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if (jmds) jmds.name = 'jmds' // 东方明珠塔 const dfmzt = group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if (dfmzt) dfmzt.name = 'dfmzt' T.scene.add(group) T.toSceneCenter(group) T.ray(group.children, (meshList) => { console.log('meshList', meshList); }) T.animate() })
T是场景的构建函数,主要创建了场景,镜头,控制器,灯光等基础信息,并且监听控制器变化时修改灯光位置
在使用第三方模型的时候,总有一些不尽人意的地方,比如模型加载后,模型中心并不在3d世界的中心位置,所以就需要调整一下模型整体的位置,toSceneCenter 方法是自定义的一个让模型居中的方法,通过BOX3获取到模型的包围盒,获取到模型的中心点坐标信息,再取反,就会得到模型中心点在3d世界的位置信息
toSceneCenter
// 获取包围盒 getBoxInfo(mesh) { const box3 = new THREE.Box3() box3.expandByObject(mesh) const size = new THREE.Vector3() const center = new THREE.Vector3() // 获取包围盒的中心点和尺寸 box3.getCenter(center) box3.getSize(size) return { size, center } } toSceneCenter(mesh) { const { center, size } = this.getBoxInfo(mesh) // 将Y轴置为0 mesh.position.copy(center.negate().setY(0)) }
没有3d设计师的支持,所有的数据都来自于模型,所以利用现有条件,收集飞线经过的点位,原理就是使用到的鼠标射线,点击模型上的某个位置并记录下来,提供给后期使用
众所周知,click的调用过程是忽略mousedown的,mouseup时候就会调用,如果单纯的想要改变视角,鼠标抬起时候也会调用click事件,所以要加一个鼠标是否移动的判断,利用控制器监听start和end时的镜头位置变化来区分鼠标是否移动
控制器部分代码:
this.controls.addEventListener('start', () => { this.controlsStartPos.copy(this.camera.position) }) this.controls.addEventListener('end', () => { this.controlsMoveFlag = this.controlsStartPos.distanceToSquared(this.camera.position) === 0 })
控制器开始变化的时候记录camera位置,跟结束时的camera的位置相减,如果为0,则表示鼠标没晃动,单纯的点击,如果不为0,说明镜头位置变化了,这时,鼠标的click回调将不会调用
射线部分代码:
ray(children: THREE.Object3D[], callback: (mesh: THREE.Intersection>[]) => void) { let mouse = new THREE.Vector2(); //鼠标位置 var raycaster = new THREE.Raycaster(); window.addEventListener("click", (event) => { mouse.x = (event.clientX / document.body.offsetWidth) * 2 - 1; mouse.y = -(event.clientY / document.body.offsetHeight) * 2 + 1; raycaster.setFromCamera(mouse, this.camera); const rallyist = raycaster.intersectObjects(children); if (this.controlsMoveFlag) { callback && callback(rallyist) } }); }
射线的回调:
let arr = [] T.ray(group.children, (meshList) => { console.log('meshList', meshList); arr.push(...meshList[0].point.toArray()) console.log(JSON.stringify(arr)); })
收集后的顶点信息:
这部分的工作只不过判断鼠标是否移动的部分不一样而已。
有了飞线具体经过的点位时候,要将这些点位细化,这时就要讲飞线的大致原理了,两点确定一条线段,获取线段上的100个点,每条飞线占用20个点位,每个点位创建一个着色器,用于绘制飞线的组成部分,当更新时候,飞线的首个点向下一个点前进,一次往后20个点都往前前进一次,循环往复一直到飞线的最后一个组成部分到达线段的最后一个点,飞线占用的点位数量决定飞线的长度,将线段分为多少个顶点,决定飞线的疏密程度,像图中这样的疏密度,就是单个线段的点位分少了,这个可以优化的,vector3.distanceTo(vector3)即可判断两个线段的长度,通过不同的长度,决定细化线段的点,当然,线段的顶点信息越多,对gpu的消耗越大
vector3.distanceTo(vector3)
flyLineData.forEach((data: number[]) => { const points: THREE.Vector2[] = [] for (let i = 0; i < data.length / 3; i++) { const x = data[i * 3] const z = data[i * 3 + 2] const point = new THREE.Vector2(x, z) points.push(point) } const curve = new THREE.SplineCurve(points); // 此处决定飞线每个点的疏密程度,数值越大,对gpu的压力越大 const curvePoints = curve.getPoints(100); const flyPoints = curvePoints.map((curveP: THREE.Vector2) => new THREE.Vector3(curveP.x, 0, curveP.y)) // const l = points.length - 1 const flyGroup = T._Fly.setFly({ index: Math.random() > 0.5 ? 50 : 20, num: 20, points: flyPoints, spaced: 50, // 要将曲线划分为的分段数。默认是 5 starColor: new THREE.Color(Math.random() * 0xffffff), endColor: new THREE.Color(Math.random() * 0xffffff), size: 0.5 }) flyLineGroup.add(flyGroup) })
setFly参数
interface SetFly { index: number, // 截取起点 num: number, // 截取长度 // 要小于length points: Vector3[], spaced: number // 要将曲线划分为的分段数。默认是 5 starColor: Color, endColor: Color, size: number }
endColor和starColor目前不好用,做不出渐变,不知道是不是长度不够,暂时先放放
创建flyLine做成了一个类,开箱即用,也可以加入自己的想法,调整内容,
创建flyLine之后要在render中调用
render() { this.controls.update() this.renderer.render(this.scene, this.camera); this._Fly && this._Fly.upDate() }
可配置参数有尺寸,透明度,颜色等
var color1 = params.starColor; //轨迹线颜色 青色 var color2 = params.endColor; //黄色 var color = color1.lerp(color2, i / newPoints2.length) colorArr.push(color.r, color.g, color.b);
这里是引用渐变色的位置,需要再调整一下
将模型绘制出线稿,并添加到原有模型上,这里用到LineBasicMaterial基础线条材质,和MeshLambertMaterial基础网格材质,调节颜色和不透明度。
LineBasicMaterial
MeshLambertMaterial
材质代码:
// 建筑材质 export const otherBuildingMaterial = (color: THREE.Color, opacity = 1) => { return new THREE.MeshLambertMaterial({ color, transparent: true, opacity }); } // 建筑线条材质 export const otherBuildingLineMaterial = (color: THREE.Color, opacity = 1) => { return new THREE.LineBasicMaterial( { color, depthTest: true, transparent: true, opacity } ) }
以下代码是之前对模型改造时写的对模型重命名的方法,现在我们来改造一下
// 重命名模型 // 环球金融中心 const hqjrzx = group.getObjectByName('02-huanqiujinrongzhongxin_huanqiujinrongzhongxin_0') if (hqjrzx) { hqjrzx.name = 'hqjrzx' changeModelMaterial(hqjrzx, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 上海中心 const shzx = group.getObjectByName('01-shanghaizhongxindasha_shanghaizhongxindasha_0') if (shzx) { shzx.name = 'shzx' changeModelMaterial(shzx, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 金茂大厦 const jmds = group.getObjectByName('03-jinmaodasha_jjinmaodasha_0') if (jmds) { jmds.name = 'jmds' changeModelMaterial(jmds, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } // 东方明珠塔 const dfmzt = group.getObjectByName('04-dongfangmingzhu_dongfangmingzhu_0') if (dfmzt) { dfmzt.name = 'dfmzt' changeModelMaterial(dfmzt, otherBuildingMaterial(buildColor, buildOpacity), otherBuildingLineMaterial(buildLineColor, buildLineOpacity),buildLineDeg) } T.scene.add(group) T.toSceneCenter(group) group.traverse((mesh: any) => { mesh as THREE.Mesh if (mesh.isMesh && (mesh.name.indexOf('Shanghai') !== -1 || mesh.name.indexOf('Object') !== -1)) { if (mesh.name.indexOf('Floor') !== -1) { mesh.material = floorMaterial } else if (mesh.name.indexOf('River') !== -1) { } else { changeModelMaterial(mesh, otherBuildingMaterial(otherBuildColor,0.8), otherBuildingLineMaterial(otherBuildLineColor,0.4),buildLineDeg) } } })
changeModelMaterial这个方法就是创建模型相对应的线条的方法,获取到模型的geometry,这里存着模型所有的顶点信息,索引和法向量,以此创建一个# 边缘几何体(EdgesGeometry)边缘几何体(EdgesGeometry)# 边缘几何体(EdgesGeometry);通过边缘几何体的信息创建 # 线段(LineSegments)线段(LineSegments) # 线段(LineSegments);并将创建出来的线段添加到原有模型中,因为我们的线段不需要单独处理,
changeModelMaterial
geometry
/** * * @param object 模型 * @param lineGroup 线组 * @param meshMaterial 模型材质 * @param lineMaterial 线材质 */ export const changeModelMaterial = (mesh: THREE.Mesh, meshMaterial: THREE.MeshBasicMaterial, lineMaterial: THREE.LineBasicMaterial, deg = 1): any => { if (mesh.isMesh) { if (meshMaterial) mesh.material = meshMaterial // 以模型顶点信息创建线条 const line = getLine(mesh, deg, lineMaterial) const name = mesh.name + '_line' line.name = name mesh.add(line) } } // 通过模型创建线条 export const getLine = (object: THREE.Mesh, thresholdAngle = 1, lineMaterial: THREE.LineBasicMaterial): THREE.LineSegments => { // 创建线条,参数为 几何体模型,相邻面的法线之间的角度, var edges = new THREE.EdgesGeometry(object.geometry, thresholdAngle); var line = new THREE.LineSegments(edges); if (lineMaterial) line.material = lineMaterial return line; }
对于我这种野生前端开发,没有UI和UE的支持,只能在网上找案例,那么就需要图片中的颜色,这里不得不提到一个工具色輪、調色盤產生器 | Adobe Color
这里可以根据一个颜色,调出互补色、相似色、单色等色彩信息
这个工具也可以根据一张图片,提取出主题色,包含主色、辅助色等信息
预埋的点位坐标信息获取和飞线点位获取一样的方法,标记采用的是CSS2DRenderer,将创建的element节点渲染到3d世界,3drender和2drender不在同一个图层内,所以需要新建一个dom节点,专门存放css2d的dom信息,
createScene 文件
+renderCss2D: CSS2DRenderer createRenderer(){ ... this.renderCss2D = new CSS2DRenderer({ element: this.css2dDom }); this.renderCss2D.setSize(this.width, this.height); ... } render(){ ... this.renderCss2D.render(this.scene, this.camera); ... }
export interface CameraPosInfo { pos: number[], // 预设摄像机位置信息 target: number[], // 控制器目标位置 name: string, // 预埋标记点或其他信息 tagPos?: number[], // 预埋标记点的位置信息 }
接下来就是要根据信息创建节点,遍历这些信息,并创建节点,这里有一个点需要提一下,2d图层和3d图层的关系
从图中可以看出,2d图层始终保持在3d图层的上层,然而我们在创建控制器的时候,第二个参数使用的是3d的图层, this.controls = new OrbitControls(this.camera, this.renderer.domElement),因为这一层被覆盖了,所以控制器失效了。
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
有两种解决方案,第一种是 new OrbitControls时,将第二个参数改为this.renderCss2D.domElement,还有一种方式,也就是本文采用的方式,将2d图层的css属性改变一下,忽略这个图层的任何事件。
new OrbitControls
this.renderCss2D.domElement
#css2dRender { /* 一定要加这个属性,不然2D内容点击没效果 */ pointer-events: none; }
由于pointer-events属性是可以继承的,2d图层内所有的元素都不响应事件,所以要将咱们创建的建筑tag的样式改一下
pointer-events
.build_tag { /* 一定要加这个属性,不然2D内容点击没效果 */ pointer-events: all; }
// 创建建筑标记 function createTag() { const buildTagGroup = new THREE.Group() T.scene.add(buildTagGroup) presetsCameraPos.forEach((cameraPos: CameraPosInfo, i: number) => { if (cameraPos.tagPos) { // 渲染2d文字 const element = document.createElement('li'); // 将信息存入dom节点中,如果是react或者vue写的,不用这么存,直接存data或者state element.setAttribute('data-cameraPosInfo', JSON.stringify(cameraPos)) element.classList.add('build_tag') element.innerText = `${i + 1}` // 将初始化好的dom节点渲染成CSS2DObject,并在scene场景中渲染 const tag = new CSS2DObject(element); const tagPos = new THREE.Vector3().fromArray(cameraPos.tagPos) tag.position.copy(tagPos) buildTagGroup.add(tag) } }) }
这里通过事件代理,点击到相应的建筑tag,从dom节点上获取到data-cameraPosInfo属性,然后通过tween动画处理器修改控制器的taget和镜头的position。事件代理是js基础内容,
data-cameraPosInfo
if (css2dDom) { css2dDom.addEventListener('click', function (e) { if (e.target) { if(e.target.nodeName=== 'LI') { console.dir(e); const cameraPosInfo = e.target.getAttribute('data-cameraPosInfo') if (cameraPosInfo) { const {pos,target} = JSON.parse(cameraPosInfo) T.controls.target.set(...target) T.handleCameraPos(pos) } } } }); }
handleCameraPos的代码
handleCameraPos
handleCameraPos(end: number[]) { // 结束时候相机位置 const endV3 = new THREE.Vector3().fromArray(end) // 目前相机到目标位置的距离,根据不同的位置判断运动的时间长度,从而保证速度不变 const length = this.camera.position.distanceTo(endV3) // 如果位置相同,不运行动画 if(length===0) return new this._TWEEN.Tween(this.camera.position) .to(endV3, Math.sqrt(length) * 400) .start() // .onUpdate((value) => { // console.log(value) // }) .onComplete(() => { // 动画结束的回调,可以展示建筑信息或其他操作 }) }
scene的场景不仅支持颜色和texture纹理,还支持canvas,上面的黑色背景太单调了,所以利用canvas绘制一个圆渐变填充到scene.background
createScene(){ ... const drawingCanvas = document.createElement('canvas'); const context = drawingCanvas.getContext('2d'); if (context) { // 设置canvas的尺寸 drawingCanvas.width = this.width; drawingCanvas.height = this.height; // 创建渐变 const gradient = context.createRadialGradient(this.width / 2, this.height, 0, this.width/2, this.height/2, Math.max(this.width, this.height)); // 为渐变添加颜色 gradient.addColorStop(0, '#0b171f'); gradient.addColorStop(0.6, '#000000'); // 使用渐变填充矩形 context.fillStyle = gradient; context.fillRect(0, 0, drawingCanvas.width, drawingCanvas.height); this.scene.background = new THREE.CanvasTexture(drawingCanvas) ... }