【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
    └─js
            mesh.js

websocket服务器

  • 服务器使用golang+gin+gorilla/websocket实现,代码比较简单:
    package main
    
    import (
    	"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 static
    	r.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 websocket
    		ws, err := upgrade.Upgrade(c.Writer, c.Request, nil)
    		if err != nil {
    			log.Fatalln(err)
    		}
    		// release source
    		defer 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 Message
    			ws.WriteMessage(websocket.TextMessage, p)
    			// c.Writer.Write(p)
    		case websocket.BinaryMessage:
    			// Handle Binary Data
    			fmt.Println("handle binary data")
    			mesh = p // 这里直接假定客户端传过来的是navmesh数据
    			ws.WriteMessage(websocket.BinaryMessage, mesh)
    		case websocket.CloseMessage:
    			// Websocket close
    			fmt.Println("close websocket connection")
    			return
    		case websocket.PingMessage:
    			// Websocket ping
    			fmt.Println("ping")
    			ws.WriteMessage(websocket.PongMessage, []byte("ping"))
    		case websocket.PongMessage:
    			// Websocket pong
    			fmt.Println("pong")
    			ws.WriteMessage(websocket.PongMessage, []byte("pong"))
    		default:
    			// Unhandled message type
    			fmt.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 array
        meshDraw(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
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电视剧电影原声背景音乐,经典影视配乐片段音效合集

一、素材描述 本套影视配乐素材&#xff0c;大小1.89G&#xff0c;27个压缩文件。 二、素材目录 宰相刘罗锅配乐片段.rar 影视配乐65首.rar 太极张三丰原声.rar 东邪西毒原声配乐15首.rar 东方不败之风云再起配乐24首.rar 东方不败原声配乐16首.rar 电影大话西游原声配…

Ubuntu18.04解决有线网卡连接问题(不更新内核成功版)

https://www.realtek.com/Download/List?cate_id584 &#xff08;需要翻一下&#xff09; 不想自己去下载&#xff0c;直接去我资源里下载我上传的包就好啦(&#x1f602;&#x1f602;&#x1f602;刚刚看了下别人下载要VIP还是自己去网站下很快的) 下载后解压&#xff0c;在…

Spring MVC(建立连接 + 请求)

文章目录 一、建立客户端和服务器的连接二、如何构造请求&#xff08;传参&#xff09;2.1 构造请求方式 参数通用注解2.2 传递单个参数2.3 传递多个参数2.4 传递数组/集合2.5 传递对象2.6 传递JSON 三、相关的其他请求操作3.1 获取URL中的参数 PathVariable3.2 上传文件 Requ…

HCIP-Datacom-ARST自选题库_07_割接【35道题】

一、单选题 1.在割接的测试阶段&#xff0c;符合以下哪一种情况的可以判断为割接成功? 网络承载的上层应用业务测试正常 网络设备的配置查看结果正常 网络流量路径正常 路由协议运行正常 2.在割接的测试阶段中&#xff0c;表明已经完成测试的标准是: IP设备的配置查看结…

Docker 直接运行一个 Alpine 镜像

由于镜像很小&#xff0c;下载时间往往很短&#xff0c;读者可以直接使用 docker run 指令直接运行一个 Alpine 容器&#xff0c;并指定运行的 Linux 指令&#xff0c;例如&#xff1a; PS C:\Users\yhu> docker run alpine echo 123 Unable to find image alpine:latest lo…

QTreeView学习 branch 虚线设置

1、方法一&#xff1a; #include <QStyleFactory> ui.treeView->setStyle(QStyleFactory::create("windows")); 2、方法二&#xff1a; QString strtyle2 R"( QTreeView::branch:has-siblings:!adjoins-item { border-image: url(:/TreeViewDe…

Nios-II编程

文章目录 一硬件部分设计1Qsys2Quartus 二软件1Nios-II Eclipse 三运行项目及效果1配置 FPGA 一硬件部分设计 1Qsys 1创建一个项目 2点击 Tools 下拉菜单下的 Platform Designer 工具&#xff0c;启动 Platform Designer 后&#xff0c;点击 File-save&#xff0c;在文件名中…

易图讯智慧公安警用三维电子沙盘系统

智慧公安警用三维电子沙盘系统是一个结合现代科技手段&#xff0c;为公安部门提供全面、高效、智能的警务管理解决方案的系统。该系统以“情报大数据、指挥扁平化、勤务可视化、情指勤一体化”为设计思想&#xff0c;整合了多台设备、有无线通讯、短信平台、天网、交通平台、治…

活字格中如何打开指定文件夹

如何使用活字格打开指定文件夹 活字格是一款功能强大的电子表格软件&#xff0c;除了基本的表格计算功能之外&#xff0c;还提供了丰富的扩展功能&#xff0c;可以用来实现各种自动化操作。例如&#xff0c;我们可以使用活字格来打开指定的文件夹。 以下是具体的操作步骤&…

重学JavaScript高阶知识点(三)—— 详解Js中的内存管理

详解Js中的内存管理 1. 简介2. 内存生命周期3. JavaScript 的内存分配4. 垃圾回收 1. 简介 很多底层语言一般都有底层的内存管理接口&#xff0c;比如 C语言&#xff0c;可以调用对应的API去创建和释放内存空间。意思是需要手动去创建和释放内存空间&#xff0c;很明显&#x…

【C++11】列表初始化、右值引用的详细讲解(上)

前言 在一开始学C之前我们就简单的了解了一下C的发展历史。 相比较而言&#xff0c;C11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全&#xff0c;不仅功能更强大&#xff0c;而且能提升程序员的开发效率加了许多特性&#xff0c;约140个新特性。使得C…

电商核心技术揭秘55:社群与粉丝经济的结合

相关系列文章 电商技术揭秘相关系列文章合集&#xff08;1&#xff09; 电商技术揭秘相关系列文章合集&#xff08;2&#xff09; 电商技术揭秘相关系列文章合集&#xff08;3&#xff09; 电商技术揭秘四十一&#xff1a;电商平台的营销系统浅析 电商技术揭秘四十二&#…

OpenAI下周发布更新;TikTok将自动标记AIGC;智谱AI亮相2024 ICLR

OpenAI 官宣下周举办直播发布更新 OpenAI 今日凌晨官宣&#xff0c;将在当地时间 5 月 13 日上午十点&#xff08;北京时间 5 月 14 日凌晨两点&#xff09;在官网进行直播&#xff0c;届时将演示一些 ChatGPT 和 GPT-4 的更新。 OpenAI CEO Sam Altman 补充表示&#xff0c;届…

【论文润色心得】博士生的福音来啦!

&#x1f31f;【论文润色心得】博士生的福音来啦&#xff01;&#x1f393; &#x1f4da; 投稿屡遭拒绝&#xff1f;语言关难过&#xff1f;看这里&#xff0c;我找到了解决之道&#xff01;&#x1f469;‍&#x1f3eb; &#x1f4a1; 我的润色经历&#xff0c;从拒稿到接…

springboot整合rabbitmq的不同工作模式理解

前提是已经安装并启动了rabbitmq&#xff0c;并且项目已经引入rabbitmq&#xff0c;完成了配置。 不同模式所需参数不同&#xff0c;生产者可以根据参数不同使用重载的convertAndSend方法。而消费者均是直接监听某个队列。 不同的交换机是实现不同工作模式的关键组件.每种交换…

excel常见图表大全

Excel图表是一种以图形形式呈现数据的工具&#xff0c;它将数字和统计信息转化为直观的视觉元素&#xff0c;如线图、柱状图、饼图等。这些图表可以帮助人们更容易地理解数据的趋势、关系和模式。 使用场景 Excel图表广泛应用于各个领域&#xff0c;包括&#xff1a; 商务分…

鸿蒙开发学习:初探【ArkUI-X】

ArkTS 是华为自研的开发语言。它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配 ArkUI 框架&#xff0c;扩展了声明式 UI 、状态管理等相应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨端应用。 ArkUI-X 进一步将 ArkUI 扩展到了多个 OS 平台…

【三十一】springboot+easyExcel实现多文件导出压缩包

互相交流入口地址 整体目录&#xff1a; 【一】springboot整合swagger 【二】springboot整合自定义swagger 【三】springboot整合token 【四】springboot整合mybatis-plus 【五】springboot整合mybatis-plus 【六】springboot整合redis 【七】springboot整合AOP实现日志操作 【…

springcloud -nacos实战

一、nacos 功能简介 1.1.什么是Nacos&#xff1f; 官方简介&#xff1a;一个更易于构建云原生应用的动态服务发现(Nacos Discovery )、服务配置(Nacos Config)和服务管理平台。 Nacos的关键特性包括&#xff1a; 服务发现和服务健康监测动态配置服务动态DNS服务服务及其元数…

OpenHarmony 实战开发——如何编译OpenHarmony自带APP

概述 OpenHarmony 的主干代码是开源社区的重要学习资源&#xff0c;对于想进行应用开发和熟悉 OpenHarmony 能力的同学主干代码是非常重要的资源&#xff0c;在主干代码的 applications 目录里聚集了很多原生的应用实现&#xff0c;那么如何编译这些代码就是我们这篇文章的主要…