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

目录

说在前面

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

目录结构

shell 复制代码
D:.
│  go.mod
│  go.sum
│  main.go // websocket server
└─public
    │  index.html
    └─js
            mesh.js

websocket服务器

  • 服务器使用golang+gin+gorilla/websocket实现,代码比较简单:

    go 复制代码
    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 复制代码
    <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
    js 复制代码
    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
相关推荐
流星白龙5 分钟前
【C++习题】10.反转字符串中的单词 lll
开发语言·c++
尘浮生12 分钟前
Java项目实战II基于微信小程序的校运会管理系统(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven·intellij-idea
MessiGo13 分钟前
Python 爬虫 (1)基础 | 基础操作
开发语言·python
Tech Synapse18 分钟前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
乌啼霜满天24927 分钟前
JDBC编程---Java
java·开发语言·sql
Myli_ing37 分钟前
考研倒计时-配色+1
前端·javascript·考研
色空大师39 分钟前
23种设计模式
java·开发语言·设计模式
余道各努力,千里自同风40 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave1 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js