【recast-navigation-js】通过websocket获取navmesh数据并初始化

目录

  • 说在前面
  • 目录结构
  • websocket服务器
  • 前端
  • 结果

说在前面

  • 操作系统:windows 11
  • 浏览器:edge版本 124.0.2478.97
  • recast-navigation-js版本:0.29.0
  • golang版本:1.21.5

目录结构

D:.
│  go.mod
│  go.sum
│  main.go // websocket server
└─public│  index.html└─jsmesh.js

websocket服务器

  • 服务器使用golang+gin+gorilla/websocket实现,代码比较简单:
    package mainimport ("fmt""log""net/http""github.com/gin-gonic/gin""github.com/gorilla/websocket"
    )var upgrade = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true},
    }func main() {r := gin.Default()// nav mesh data // 这里只是举例,可以根据需求从文件读取或者生成var mesh []byte// html staticr.Static("/public", "./public")r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.GET("/ws", func(c *gin.Context) {// upgrade to websocketws, err := upgrade.Upgrade(c.Writer, c.Request, nil)if err != nil {log.Fatalln(err)}// release sourcedefer ws.Close()go func() {// connect done<-c.Done()fmt.Println("ws lost connection")}()messageType, p, err := ws.ReadMessage()if err != nil {fmt.Println(err)return}switch messageType {case websocket.TextMessage:// Handle Text Messagews.WriteMessage(websocket.TextMessage, p)// c.Writer.Write(p)case websocket.BinaryMessage:// Handle Binary Datafmt.Println("handle binary data")mesh = p // 这里直接假定客户端传过来的是navmesh数据ws.WriteMessage(websocket.BinaryMessage, mesh)case websocket.CloseMessage:// Websocket closefmt.Println("close websocket connection")returncase websocket.PingMessage:// Websocket pingfmt.Println("ping")ws.WriteMessage(websocket.PongMessage, []byte("ping"))case websocket.PongMessage:// Websocket pongfmt.Println("pong")ws.WriteMessage(websocket.PongMessage, []byte("pong"))default:// Unhandled message typefmt.Printf("unknown message type: %d\n", messageType)return}})r.Run() // listen and serve on 0.0.0.0:8080
    }
    

前端

  • 使用three.js绘制数据
  • index.html
    <html>
    <script type="importmap">{"imports": {"@recast-navigation/core": "https://unpkg.com/@recast-navigation/core@0.29.0/dist/index.mjs","@recast-navigation/wasm": "https://unpkg.com/@recast-navigation/wasm@0.29.0/dist/recast-navigation.wasm-compat.js","@recast-navigation/generators": "https://unpkg.com/@recast-navigation/generators@0.29.0/dist/index.mjs","@recast-navigation/three": "https://unpkg.com/@recast-navigation/three@0.29.0/dist/index.mjs","three": "https://unpkg.com/three@0.164.0/build/three.module.js","three/examples/jsm/controls/OrbitControls": "https://unpkg.com/three@0.164.0/examples/jsm/controls/OrbitControls.js"}}</script>
    <script src="./js/mesh.js" type="module" ></script><style>body {margin: 0;overflow: hidden;}canvas {width: 100%;height: 100vh;}
    </style>
    </html>
    
  • mesh.js
    主要流程
    1. 在websocket建立连接成功后,将烘焙的navmesh数据发送至服务器
    2. 服务器在收到数据后直接返回(模拟通信过程,实际上服务器也可以从文件读取然后返回给前端)
    3. 前端接收到数据后通过importNavMesh接口初始化新的navmesh
    4. 使用threejs重新绘制新的navmesh
    import * as THREE from 'three';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    import {init as initRecastNavigation,NavMeshQuery,
    } from '@recast-navigation/core';
    import { generateSoloNavMesh } from '@recast-navigation/generators';
    import {DebugDrawer,getPositionsAndIndices,
    } from '@recast-navigation/three';
    import { exportNavMesh, importNavMesh } from '@recast-navigation/core';// initialise recast-navigation
    await initRecastNavigation();var ws = new WebSocket("ws://127.0.0.1:8080/ws");
    ws.binaryType = "arraybuffer"; // use arraybuffer
    ws.onopen = function () {console.log("websocket connected.");meshInit();
    };
    ws.onmessage = function (e) {console.log("websockt data:", e);var uint8_msg = new Uint8Array(e.data); // convert to uint8 arraymeshDraw(uint8_msg);
    };
    ws.onerror = function () {console.log("websocket error.");
    };// setup scene
    const renderer = new THREE.WebGLRenderer();
    document.body.appendChild(renderer.domElement);const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera();
    camera.position.set(10, 10, -10);const orbitControls = new OrbitControls(camera, renderer.domElement);// add some meshes
    const ground = new THREE.Mesh(new THREE.BoxGeometry(10, 1, 10),new THREE.MeshBasicMaterial({ color: '#333' })
    );
    ground.position.set(0, -0.5, 0);scene.add(ground);const boxOne = new THREE.Mesh(new THREE.BoxGeometry(8, 2, 1),new THREE.MeshBasicMaterial({ color: '#555' })
    );
    boxOne.rotation.y = Math.PI / 4;
    boxOne.position.set(-2, 1, 0);
    scene.add(boxOne);const boxTwo = new THREE.Mesh(new THREE.BoxGeometry(8, 2, 1),new THREE.MeshBasicMaterial({ color: '#555' })
    );
    boxTwo.rotation.y = Math.PI / 4;
    boxTwo.position.set(2, 1, 0);
    scene.add(boxTwo);// get the positions and indices that we want to generate a navmesh from
    const [positions, indices] = getPositionsAndIndices([ground,boxOne,boxTwo,
    ]);// generate a solo navmesh
    const cs = 0.05;
    const ch = 0.05;
    const walkableRadius = 0.2;
    const { success, navMesh } = generateSoloNavMesh(positions, indices, {cs,ch,walkableRadius: Math.round(walkableRadius / ch),
    });// debug draw the navmesh
    const debugDrawer = new DebugDrawer();
    debugDrawer.drawNavMesh(navMesh);
    scene.add(debugDrawer);// compute a path
    const start = { x: -4, y: 0, z: -4 };
    const end = { x: 4, y: 0, z: 4 };const navMeshQuery = new NavMeshQuery(navMesh);
    const { path } = navMeshQuery.computePath(start, end);// draw the path start
    const startMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1),new THREE.MeshBasicMaterial({ color: 'blue' })
    );
    startMarker.position.set(start.x, start.y + 0.1, start.z);
    scene.add(startMarker);// draw the path end
    const endMarker = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.1, 0.1),new THREE.MeshBasicMaterial({ color: 'green' })
    );
    endMarker.position.set(end.x, end.y + 0.1, end.z);
    scene.add(endMarker);// draw the path line
    const line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(path.map(({ x, y, z }) => new THREE.Vector3(x, y, z))),new THREE.LineBasicMaterial({ color: 'blue' })
    );
    line.position.y += 0.1;
    scene.add(line);// handle resizing
    const onWindowResize = () => {debugDrawer.resize(window.innerWidth, window.innerHeight);camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);
    };
    onWindowResize();window.addEventListener('resize', onWindowResize);// animate
    const animate = () => {requestAnimationFrame(animate);renderer.render(scene, camera);
    };animate();// send nav mesh data to server
    function meshInit() {const navMeshExport = exportNavMesh(navMesh);ws.send(navMeshExport);
    }// rebuild nav mesh from server and draw
    function meshDraw(bin) {const tmpNavMesh = importNavMesh(bin);const tmpDebugDrawer = new DebugDrawer();tmpDebugDrawer.drawNavMesh(tmpNavMesh.navMesh);tmpDebugDrawer.position.z += 10;scene.add(tmpDebugDrawer);
    }
    

结果

  • 左侧为使用服务器数据重绘的navmesh
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1421092.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

HCIE学习笔记----OSPF详解

OSPF邻居建立的条件 OSPF建立邻居“41”条件总结 4个一致 一个不一致 1.保证接口的前缀 网络信息一致 2.保证ospf区域号和区域类型一致 3.hello包间隔时间和死亡时间一致 4.认证类型和认证认证信息一致 5.路由器的ID不一致 保证唯一性 一-----OSPF 邻接关系建立过程与状…

LeetCode题练习与总结:二叉树的中序遍历--94

一、题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xff1a;roo…

微服务思想以及实现

文章目录 前言一、什么时候需要拆分微服务1. 创业型项目2. 大型项目 二、怎么拆1. 拆分目标2. 拆分方式 三、微服务之间远程调用1. 实现方式2. 手动发送Http请求&#xff08;RestTemplate&#xff09;3. 服务注册中心3.1 原理3.2 Nacos注册中心3.3 服务注册3.4 服务发现(Discov…

ExcelVBA在选择区域(有合并)中删除清除空行

【问题】 关于删除空行&#xff0c;以前是用函数来完成工作的&#xff0c; 今天有人提出问题&#xff0c;传来这个文件&#xff0c; 现有数据&#xff0c;1w多行&#xff0c;其中有部分列有不同合并单元格&#xff0c;跨行也不一样。如果要进行筛选删除空行&#xff0c;有一定的…

从 Oracle 到 TiDB,国有大行打造本地生活 APP 新体验

导读 本文介绍了某国有大行推出的本地生活服务类 APP 在数字时代的创新应用实践。该 APP 利用金融科技和互联网平台模式&#xff0c;打造“金融非金融”的线上生态服务平台&#xff0c;满足了用户多样化的生活需求。为应对用户增长和数据量增加带来的挑战&#xff0c;该 APP 决…

C语言之指针初阶

目录 前言 一、内存与地址的关系 二、指针变量 三、野指针 四、const 五、传值调用与传址调用 总结 前言 本文主要介绍C语言指针的一些基础知识&#xff0c;为后面深入理解指针打下基础&#xff0c;因此本文内容主要包括内存与地址的关系&#xff0c;指针的基本语法&…

高精度原理介绍及代码实现

目录 高精度 引入 使用场景 实现原理 高精度加法 数据存储 加法实现 总代码 高精度减法 与加法的不同点&#xff1a; 总代码 高精度乘法 总代码 高精度除法 总结 总注意点 减法注意点 高精度 引入 所谓高精度并不是很高级难懂的东西&#xff0c;只是对传统的…

使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫

今天,明月给大家再次详细讲解一下,明月在使用 CloudFlare 后如何才能不影响搜索引擎蜘蛛爬虫对站点的抓取,因为这是很多首次使用 CloudFlare 的站长们容易忽略和触犯的问题,并不是 CloudFlare 不友好,而是 CloudFlare 的防火墙(WAF)实在是太给力。其实在【CloudFlare 如…

SQLZOO:The JOIN operation

数据表&#xff1a;game-gaol-eteam game idmdatestadiumteam1team210018 June 2012National Stadium, WarsawPOLGRE10028 June 2012Stadion Miejski (Wroclaw)RUSCZE100312 June 2012Stadion Miejski (Wroclaw)GRECZE100412 June 2012National Stadium, WarsawPOLRUS... goal …

每日OJ题_贪心算法四④_力扣397. 整数替换

目录 力扣397. 整数替换 解析代码 力扣397. 整数替换 397. 整数替换 难度 中等 给定一个正整数 n &#xff0c;你可以做如下操作&#xff1a; 如果 n 是偶数&#xff0c;则用 n / 2替换 n 。如果 n 是奇数&#xff0c;则可以用 n 1或n - 1替换 n 。 返回 n 变为 1 所需…

最大子矩阵:前缀和、动态规划

最近在学习动态规划&#xff0c;在牛客上刷题时碰到了这一题。其实最初的想法是暴力和前缀和&#xff0c;但是时间复杂度极高&#xff0c;需要套4层循环。后来去网上搜了一下相关的题解和做法&#xff0c;进而了解到了前缀和&#xff0b;线性动态规划的做法。但是在成功做出这题…

排除对象属性序列化的三种方式

说明&#xff1a;在项目里&#xff0c;经常可以看到以下日志内容&#xff0c;将对象序列化后直接打印出来&#xff0c;观察对象数据&#xff0c;判断当前处理逻辑正确与否。 &#xff08;以下信息来自&#xff1a;https://www.tl.beer/randbankcard.html生成器&#xff0c;信息…

python跟C++选哪个?

选择使用Python还是C取决于你的具体需求和项目背景。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 在通信工程行业…

第六节笔记及作业----Lagent AgentLego 智能体应用搭建

关于 Agent 的相关理论 大语言模型存在一些局限性&#xff0c;比如会出现幻觉问题、有时效性问题以及可靠性问题。智能体的定义是具备感知、决策和行动能力的实体。智能体主要由感知部分、大脑部分和动作部分组成。智能体有多种类型&#xff0c;如 ReAct 类型&#xff08;侧重…

TCP服务器实现将客服端发送的信息广播发送(使用内核链表管理客户端信息)

目录 1.服务器端实现思路 2.服务器端代码 3.客户端代码 4.内核链表代码 5.运行格式 一、服务器端 二、客户端 6.效果 1.服务器端实现思路 Tcp广播服务初始化 等待客户端连接 广播发送 2.服务器端代码 #include "list.h" #include <signal.h> #def…

视频打赏系统源码

地球号&#xff1a;xiaobao0214520(WX) 支付对接&#xff0c;盒子推广&#xff0c;域名防封&#xff0c;等一系列功能皆为正常&#xff0c;后台账号密码:地球号&#xff1a;xiaobao0214520(WX)&#xff0c;测试网站,可以定制哦支付对接&#xff0c;盒子推广&#xff0c;域名防…

免费思维13招之七:空间型思维

免费思维13招之七:空间型思维 本篇给你带来的是空间型思维。 空间型思维,具体分为内部空间型思维和外部空间型思维。 什么叫内部空间型思维呢? 内部空间型就是充分利用现有空间或资源为社会提供免费服务,积累人气,增加流量,从而带动消费。 为什么你生意不好?为什么你…

python数据分析——matplotlib可视化基础

参考资料&#xff1a;活用pandas库 # 导入库 import pandas as pd import matplotlib.pyplot as plt # 导入数据 anscombepd.read_csv(r"...\seaborn常用数据案例\anscombe.csv") anscombe.head() 大多数基本图表的名字以plt.plot开头。 # 创建数据子集 # 只包含数…

图片转word如何转换?

要将图片转换为Word文档&#xff0c;你可以使用以下方法之一&#xff1a; 以上这些方法都可以帮助你将图片中的文本转换为可编辑的Word文档&#xff0c;你可以根据自己的喜好和需求选择其中一种方法来操作。 使用OCR软件或在线工具&#xff1a;有许多OCR&#xff08;Optical Ch…

【Spring】验证 @ServerEndpoint 的类成员变量线程安全

文章目录 前言猜想来源验证方法Controller 的情况ServerEndpoint 的情况 后记 前言 最近有 websocket 的需求。探索 ServerEndpoint 的类成员变量特点。 这里类比 Controller 讨论 ServerEndpoint 类成员变量是否线程安全。 猜想来源 网上的教程大多数都这么展示程序&#…