Win11通过Nginx启动3DTiles,通过html加载3DTiles详细操作流程

win下安装Negix搭建3dtiles


一、安装Negix
下载解压Nginx
  官网下载地址:http://nginx.org/en/download.html
  选择
  Stable version——> nginx/Windows-1.28.0
安装运行Nginx
  尽量在cmd窗口启动,不要直接双击nginx.exe,这样会导致修改配置后重启、停止nginx无效,需要手动关闭任务管理器内的所有nginx进程,再启动才可以,就很麻烦。
  Win+R快捷键cmd运行,在 D:\nginx-1.22.0\nginx-1.22.0 目录下,执行 nginx.exe 回车即可:
Nginx常用命令

nginx.exe         #启动运行
nginx -s stop     #停止
nginx -s quit     #退出
nginx -s reload   #重新加载

二、配置Negix,加载3DTiles地图信息
路径修改为自己的路径
启动Nginx后请求:
http://localhost/out/tileset.json
或者 http://localhost:80/out/tileset.json
返回json

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    
    log_format main '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent"';
    
    access_log  logs/access.log  main;
    error_log   logs/error.log warn;
    
    sendfile        off;
    tcp_nopush      off;
    keepalive_timeout  65;
    server_tokens off;

    # 移除了重复的json类型定义,保留唯一特殊3D Tiles类型
    types {
        application/octet-stream b3dm pnts i3dm cmpt;
        model/gltf+json          gltf;
        model/gltf-binary        glb;
    }

    server {
        listen       80;
        server_name  192.168.0.138;
        root         C:/obgs/jhdata;
        
        # 全局CORS设置(带always确保所有响应都包含)
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;

        location /out/ {
            alias C:/obgs/jhdata/out1/;
            
            # OPTIONS预检请求处理
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Max-Age' 1728000 always;
                add_header 'Content-Type' 'text/plain; charset=utf-8' always;
                add_header 'Content-Length' 0 always;
                return 204;
            }
            
            # JSON文件单独处理(不再需要重复定义MIME类型)
            location ~ \.json$ {
                # 确保返回正确的Content-Type
                default_type application/json;
            }
        }
    }
}

三、编写html页面加载3dtiles内容
通过调用官网api和本地api俩种方式实现

官网api:

 <script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> 

本地资源api

<script src="./Cesium-1.110.1/Build/Cesium/Cesium.js"></script>
<link href="./Cesium-1.110.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet">

Cesium官网下载地址
html代码如下:

DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>3D Tiles Viewertitle>
    
    <script src="./Cesium-1.110.1/Build/Cesium/Cesium.js">script>
    <link href="./Cesium-1.110.1/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
    <style>
        html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; }
        #cesiumContainer { width: 100%; height: 100%; }
        .loading-overlay { 
            position: absolute; top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0,0,0,0.9); color: white; 
            display: flex; flex-direction: column;
            justify-content: center; align-items: center;
            z-index: 999; font-family: Arial, sans-serif;
            backdrop-filter: blur(5px);
        }
        .progress-container {
            width: 80%; max-width: 500px;
            margin: 20px 0;
            background: rgba(255,255,255,0.1);
            border-radius: 10px; overflow: hidden;
        }
        #progressBar { height: 8px; background: linear-gradient(90deg, #4CAF50, #8BC34A); width: 0%; 
                     transition: width 0.3s ease; }
        #statusText { margin-top: 20px; text-align: center; }
        .error { color: #ff6b6b; }
        #debugInfo { 
            position: absolute; bottom: 10px; right: 10px; 
            background: rgba(0,0,0,0.7); padding: 8px; 
            border-radius: 4px; font-size: 12px;
            max-width: 300px; display: none;
        }
        
        /* 控制面板样式 */
        .control-panel {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(42, 42, 42, 0.8);
            padding: 12px;
            border-radius: 5px;
            z-index: 1000;
            box-shadow: 0 0 10px rgba(0,0,0,0.5);
            color: white;
        }
        .control-panel h3 {
            margin: 0 0 10px 0;
            padding: 0;
            font-size: 16px;
            text-align: center;
        }
        .control-btn {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 8px 12px;
            margin: 5px 0;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
            transition: background 0.3s;
        }
        .control-btn:hover {
            background: #45a049;
        }
        .control-btn.active {
            background: #2196F3;
        }
        .slider-container {
            margin: 10px 0;
            width: 100%;
        }
        .slider-container label {
            display: block;
            margin-bottom: 5px;
            font-size: 12px;
        }
        .speed-slider {
            width: 100%;
        }
        
        /* 操作提示面板优化 */
        .hint-panel {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(42, 42, 42, 0.9);
            padding: 12px 15px;
            border-radius: 8px;
            z-index: 1000;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            backdrop-filter: blur(5px);
            font-size: 14px;
            line-height: 1.5;
            color: #ffffff;
            user-select: none;
            pointer-events: none;
            opacity: 0.9;
            transition: opacity 0.3s ease;
        }
        .hint-panel:hover {
            opacity: 1;
        }
        .hint-panel div {
            margin: 6px 0;
            display: flex;
            align-items: center;
        }
        .hint-icon {
            width: 20px;
            height: 20px;
            margin-right: 10px;
            fill: #ffffff;
        }

        /* 指南针优化 */
        .compass {
            position: fixed;
            top: 100px;
            right: 25px;
            width: 50px;
            height: 50px;
            background: rgba(42, 42, 42, 0.9);
            border-radius: 50%;
            z-index: 1000;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            transition: transform 0.3s ease, background 0.3s ease;
            backdrop-filter: blur(5px);
        }
        .compass:hover {
            background: rgba(66, 66, 66, 0.9);
            transform: scale(1.05);
        }
        .compass svg {
            width: 80%;
            height: 80%;
            transform-origin: center;
            transition: transform 0.2s ease;
        }
        .compass.auto-rotate {
            animation: compassRotate 3s linear infinite;
        }
        @keyframes compassRotate {
            from { transform: rotate(0deg) scale(1.05); }
            to { transform: rotate(360deg) scale(1.05); }
        }
        .compass:hover svg {
            transform: scale(1.1);
        }
        .compass.resetting {
            animation: compassReset 0.5s ease-out;
        }
        @keyframes compassReset {
            0% { transform: rotate(var(--current-rotation)) scale(1.1); }
            100% { transform: rotate(0deg) scale(1.1); }
        }

        /* 鼠标拖动样式 */
        .drag-cursor {
            cursor: grab;
        }
        .dragging {
            cursor: grabbing;
        }
    style>
head>
<body>
<div id="cesiumContainer">div>
<div class="loading-overlay">
    <h1>3D 模型加载器h1>
    <div class="progress-container">
        <div id="progressBar">div>
    div>
    <div id="statusText">正在初始化...div>
    <div id="debugInfo">div>
div>


<div class="control-panel" id="controlPanel">
    <h3>模型控制h3>
    <button class="control-btn" id="rotateToggle">开启旋转button>
    <button class="control-btn" id="resetView">重置视角button>
    <div class="slider-container">
        <label for="rotationSpeed">旋转速度: <span id="speedValue">1.0span>xlabel>
        <input type="range" min="0.1" max="3" step="0.1" value="1.0" class="speed-slider" id="rotationSpeed">
    div>
div>


<div class="hint-panel">
    <div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M20 9c-.04-4.39-3.6-7.93-8-7.93S4.04 4.61 4 9v6c0 4.42 3.58 8 8 8s8-3.58 8-8V9zm-2 0h-5V3.16c2.81.47 4.96 2.9 5 5.84zm-7-5.84V9H6c.04-2.94 2.19-5.37 5-5.84zM18 15c0 3.31-2.69 6-6 6s-6-2.69-6-6v-4h12v4z"/>svg>左键拖动 - 旋转视角div>
    <div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M13 5.08c3.06.44 5.48 2.86 5.92 5.92h3.03c-.47-4.72-4.23-8.48-8.95-8.95v3.03zM18.92 13c-.44 3.06-2.86 5.48-5.92 5.92v3.03c4.72-.47 8.48-4.23 8.95-8.95h-3.03zM11 18.92c-3.39-.49-6-3.4-6-6.92s2.61-6.43 6-6.92V2.05c-5.05.5-9 4.76-9 9.9 0 5.15 3.95 9.4 9 9.9v-3.03z"/>svg>滚轮 - 缩放视角div>
    <div><svg class="hint-icon" viewBox="0 0 24 24"><path d="M15 5l-1.41 1.41L18.17 11H8v2h10.17l-4.59 4.59L15 19l7-7-7-7zM4 5h4V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h4v-2H4V5z"/>svg>中键拖动 - 平移视角div>
div>


<div class="compass" id="compass">
    <svg viewBox="0 0 36 36">
        <circle cx="18" cy="18" r="16" stroke="#ffffff" stroke-width="1" fill="none"/>
        <path d="M18 6 L18 30 M18 6 L12 12 M18 6 L24 12" stroke="#ffffff" stroke-width="2" fill="none"/>
        <text x="18" y="12" text-anchor="middle" font-size="8" fill="#ffffff">Ntext>
    svg>
div>

<script>
    // 配置参数
    const config = {
        // cesiumToken: '替换自己token,在线组件需要',
        tilesetUrl: '替换自己json地址',
        showDebug: true,
        flyToDuration: 3.0,
        initialRotation: 45, // 默认向右旋转45度
        mouseSensitivity: 1 // 鼠标拖动灵敏度调节 (0.1-1.0)

    };

    // 初始化Viewer
    class CesiumViewer {
        constructor() {
            this.viewer = null;
            this.tileset = null;
            this.loadingEl = document.querySelector('.loading-overlay');
            this.progressEl = document.getElementById('progressBar');
            this.statusEl = document.getElementById('statusText');
            this.debugEl = document.getElementById('debugInfo');
            this.isRotating = false;
            this.rotationSpeed = 1.0;
            this.rotateHandler = null;
            this.initialView = null;
            this.compassListener = null;
            
            // 控制面板元素
            this.rotateToggleBtn = document.getElementById('rotateToggle');
            this.resetViewBtn = document.getElementById('resetView');
            this.speedSlider = document.getElementById('rotationSpeed');
            this.speedValueDisplay = document.getElementById('speedValue');
            
            // 鼠标拖动相关变量
            this.isDragging = false;
            this.lastMousePosition = null;
            this.previousAutoRotate = false;

            // 指南针元素
            this.compass = document.getElementById('compass');
            
            this.init();
        }

        async init() {
            try {
                // 设置Ion令牌
                Cesium.Ion.defaultAccessToken = config.cesiumToken;
                
                // 初始化UI
                this.updateStatus('正在加载Cesium引擎...', 10);
                
                // 检查Cesium是否加载
                if (!window.Cesium) throw new Error("Cesium库加载失败");
                
                // 创建Viewer
                this.updateStatus('正在创建3D场景...', 30);
                this.createViewer();
                
                // 检查并加载3D Tiles
                await this.checkTileset();
                await this.loadTileset();
                
                // 完成加载
                this.completeLoading();
                
                // 设置控制面板事件
                this.setupControls();
                
                // 添加鼠标拖动控制
                this.setupMouseDrag();
                
                // 禁用默认的相机控制
                this.disableDefaultCameraControls();
                
            } catch (error) {
                this.handleError(error);
            }
        }

        disableDefaultCameraControls() {
            // 禁用原始的左键拖动事件
            // this.viewer.scene.screenSpaceCameraController.enableRotate = false;
            // this.viewer.scene.screenSpaceCameraController.enableTilt = false;
            // this.viewer.scene.screenSpaceCameraController.enableLook = false;
       
        }

        createViewer() {
            // const terrainProvider = this.createTerrainProvider();
             const terrainProvider = new Cesium.EllipsoidTerrainProvider();
            this.viewer = new Cesium.Viewer('cesiumContainer', {
                terrainProvider: terrainProvider,
                imageryProvider: false, // 不使用在线影像
                // imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
                //     url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
                // }),
                animation: false,
                baseLayerPicker: false,
                fullscreenButton: true,
                geocoder: false,
                homeButton: false,
                infoBox: false,
                sceneModePicker: false,
                selectionIndicator: false,
                timeline: false,
                navigationHelpButton: false,
                shouldAnimate: true
            });
            
              // 移除默认影像层
            this.viewer.imageryLayers.remove(this.viewer.imageryLayers.get(0));
            // 设置鼠标悬停样式
            this.viewer.scene.canvas.classList.add('drag-cursor');
            
            this.viewer.scene.postProcessStages.fxaa.enabled = false;
            this.viewer.scene.screenSpaceCameraController.zoomEventTypes = [Cesium.CameraEventType.WHEEL];
            this.viewer.scene.screenSpaceCameraController.tiltEventTypes  = [Cesium.CameraEventType.LEFT_DRAG]; 
            this.viewer.scene.screenSpaceCameraController.rotateEventTypes   = [Cesium.CameraEventType.RIGHT_DRAG]; 
        }

        createTerrainProvider() {
            try {
                if (typeof Cesium.createWorldTerrain === 'function') {
                    return Cesium.createWorldTerrain({
                        requestWaterMask: true,
                        requestVertexNormals: true
                    });
                }
                return new Cesium.EllipsoidTerrainProvider();
            } catch (e) {
                console.warn("地形初始化失败:", e);
                return new Cesium.EllipsoidTerrainProvider();
            }
        }

        async checkTileset() {
            this.updateStatus('正在检查3D模型数据...', 50);
            
            try {
                const response = await fetch(config.tilesetUrl, {
                    method: 'GET',
                    mode: 'cors',
                    cache: 'no-cache'
                });
                
                if (!response.ok) throw new Error(`HTTP错误 (${response.status})`);
                
                const data = await response.json();
                if (!data.root) throw new Error("无效的tileset.json格式");
            } catch (error) {
                if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
                    console.warn("CORS问题检测到,尝试非CORS模式加载");
                    return;
                }
                throw new Error(`模型检查失败: ${error.message}`);
            }
        }

        async loadTileset() {
            this.updateStatus('正在加载3D模型...', 70);
            
            this.tileset = await Cesium.Cesium3DTileset.fromUrl(config.tilesetUrl, {
                dynamicScreenSpaceError: true,
                maximumScreenSpaceError: 2,
                dynamicScreenSpaceErrorDensity: 0.00278,
                dynamicScreenSpaceErrorFactor: 4.0,
                dynamicScreenSpaceErrorHeightFalloff: 0.25,
                cullWithChildrenBounds: true,
                cullRequestsWhileMoving: true,
                preloadFlightDestinations: false,
                preloadWhenHidden: false,
                preferLeaves: true
            });
           
            
            this.viewer.scene.primitives.add(this.tileset);
            
            this.tileset.initialTilesLoaded.addEventListener(() => {
                this.updateStatus('模型加载完成,准备渲染...', 90);
                
                // 默认向右旋转60度
                this.applyInitialRotation(config.initialRotation);
            });
            
            // 等待tileset准备就绪
            await this.viewer.zoomTo(this.tileset, new Cesium.HeadingPitchRange(0, -0.5, 0), config.flyToDuration);
            
            // 保存初始视角
            this.initialView = {
                position: this.viewer.camera.position.clone(),
                heading: Cesium.Math.toRadians(config.initialRotation),
                pitch: -0.5,
                roll: 0
            };

            // 初始化指南针
            this.updateCompass();

              // 使用更可靠的tileset准备检测方式
    await new Promise(resolve => {
        const checkReady = () => {
            if (this.tileset.boundingSphere) {
                resolve();
            } else {
                setTimeout(checkReady, 100);
            }
        };
        checkReady();
    });
    
        // 应用初始旋转
        this.applyInitialRotation(config.initialRotation);
        
        // 保存初始视角
        this.initialView = {
            position: this.viewer.camera.position.clone(),
            heading: Cesium.Math.toRadians(config.initialRotation),
            pitch: -0.5,
            roll: 0
        };
        }

      applyInitialRotation(degrees) {
    if (!this.tileset) return;
    const tryApplyRotation = () => {
        try {
            if (this.tileset.boundingSphere && this.tileset.boundingSphere.center) {
                const radians = Cesium.Math.toRadians(degrees);
                const boundingSphere = this.tileset.boundingSphere;
                const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
                    boundingSphere.center
                );
                
                if (center) {
                    // 使用 rotate 而不是直接设置朝向
                    this.viewer.camera.rotate(center, radians);
                    return true;
                }
            }
            return false;
        } catch (e) {
            console.warn("旋转应用失败:", e);
            return false;
        }
    };
    if (!tryApplyRotation()) {
        const retryInterval = setInterval(() => {
            if (tryApplyRotation()) {
                clearInterval(retryInterval);
            }
        }, 100);
        
        setTimeout(() => clearInterval(retryInterval), 15000);
    }
}


        setupControls() {
            const self = this;
            
            // 旋转开关
            this.rotateToggleBtn.addEventListener('click', () => {
                self.toggleRotation();
            });
            
            // 重置视角
            this.resetViewBtn.addEventListener('click', () => {
                self.resetView();
                self.compass.classList.remove('auto-rotate');
            });
            
            // 旋转速度调节
            this.speedSlider.addEventListener('input', (e) => {
                self.rotationSpeed = parseFloat(e.target.value);
                self.speedValueDisplay.textContent = self.rotationSpeed.toFixed(1);
                if (self.isRotating) {
                    self.stopRotation();
                    self.startRotation();
                }
            });
            
            // 指南针点击事件
            this.compass.addEventListener('click', () => this.resetToNorth());
            this.compass.addEventListener('touchstart', (e) => {
                e.preventDefault();
                this.resetToNorth();
            });
        }

        updateCompass() {
            if (!this.viewer) return;
            
            // 移除之前的监听器
            if (this.compassListener) {
                this.viewer.scene.postRender.removeEventListener(this.compassListener);
            }
            
            this.compassListener = this.viewer.scene.postRender.addEventListener(() => {
                if (this.viewer && this.viewer.camera) {
                    // 获取当前相机朝向角度(弧度)
                    const heading = this.viewer.camera.heading;
                    // 转换为角度并取反(因为指南针需要反向旋转)
                    const degrees = -Cesium.Math.toDegrees(heading);
                    
                    // 设置自定义属性用于复位动画
                    this.compass.style.setProperty('--current-rotation', `${degrees}deg`);
                    
                    // 如果不是自动旋转状态,更新指南针角度
                    if (!this.isRotating) {
                        this.compass.style.transform = `rotate(${degrees}deg)`;
                    }
                }
            });
        }
        
        resetToNorth() {
            if (!this.viewer || !this.tileset) return;
            
            this.stopRotation();
            this.compass.classList.remove('auto-rotate');
            this.compass.classList.add('resetting');
            
            // 计算模型中心点
            const boundingSphere = this.tileset.boundingSphere;
            const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
                boundingSphere.center
            );
            
            // 重置朝向正北
            this.viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(
                0, // 朝向正北
                this.viewer.camera.pitch, // 保持当前俯仰角
                this.viewer.camera.positionCartographic.height // 保持当前高度
            ));
            
            // 动画结束后移除复位状态
            setTimeout(() => {
                this.compass.classList.remove('resetting');
            }, 500);
        }
        
     setupMouseDrag() {
    const scene = this.viewer.scene;
    const canvas = scene.canvas;
    
    // 新增惯性效果相关变量
    this.inertia = {
        enabled: true,
        friction: 0.90,  // 摩擦系数 (0.9 = 10%速度损失/帧)
        velocityX: 0,
        velocityY: 0,
        lastTime: 0
    };
    
    // 鼠标按下事件
    canvas.addEventListener('mousedown', (e) => {
        if (e.button !== 0) return;
        
        // 停止任何惯性动画
        cancelAnimationFrame(this.inertia.animationFrame);
        
        if (this.isRotating) {
            this.previousAutoRotate = true;
            this.stopRotation();
            this.compass.classList.remove('auto-rotate');
        } else {
            this.previousAutoRotate = false;
        }
        
        this.isDragging = true;
        document.body.classList.add('dragging');
        this.lastMousePosition = new Cesium.Cartesian2(e.clientX, e.clientY);
        
        // 重置惯性速度
        this.inertia.velocityX = 0;
        this.inertia.velocityY = 0;
        this.inertia.lastTime = performance.now();
        
        e.preventDefault();
    }, false);
    
    // 鼠标移动事件
    canvas.addEventListener('mousemove', (e) => {
        if (!this.isDragging) return;
        
        const currentTime = performance.now();
        const timeDelta = currentTime - this.inertia.lastTime;
        this.inertia.lastTime = currentTime;
        
        const currentPosition = new Cesium.Cartesian2(e.clientX, e.clientY);
        const movement = new Cesium.Cartesian2();
        Cesium.Cartesian2.subtract(currentPosition, this.lastMousePosition, movement);
        
        // 计算当前速度 (像素/毫秒)
        if (timeDelta > 0) {
            this.inertia.velocityX = movement.x / timeDelta;
            this.inertia.velocityY = movement.y / timeDelta;
        }
        
        this.rotateCamera(movement.x, movement.y);
        this.lastMousePosition = currentPosition;
    }, false);
    
    // 鼠标释放事件 - 添加惯性动画
    document.addEventListener('mouseup', (e) => {
        if (!this.isDragging || e.button !== 0) return;
        
        this.isDragging = false;
        document.body.classList.remove('dragging');
        
        // 如果有足够速度,启动惯性动画
        if (this.inertia.enabled && 
            (Math.abs(this.inertia.velocityX) > 0.1 || Math.abs(this.inertia.velocityY) > 0.1)) {
            this.startInertiaAnimation();
        }
        
        // 恢复自动旋转
        if (this.previousAutoRotate) {
            this.startRotation();
            this.compass.classList.add('auto-rotate');
        }
    }, false);
    
    // 鼠标移出时停止
    canvas.addEventListener('mouseleave', () => {
        if (this.isDragging) {
            this.isDragging = false;
            document.body.classList.remove('dragging');
            cancelAnimationFrame(this.inertia.animationFrame);
        }
    }, false);
    
    // 鼠标悬停样式
    canvas.addEventListener('mouseenter', () => {
        if (!this.isDragging) {
            document.body.classList.add('drag-cursor');
        }
    });
    
    canvas.addEventListener('mouseleave', () => {
        if (!this.isDragging) {
            document.body.classList.remove('drag-cursor');
        }
    });
}

// 新增旋转相机方法
        rotateCamera(deltaX, deltaY) {
            if (!this.tileset || !this.tileset.boundingSphere) return;
            
            try {
                const boundingSphere = this.tileset.boundingSphere;
                const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
                    boundingSphere.center
                );
                
                if (!center) return;
                
                // 计算新的朝向角度(考虑当前角度)
                const headingDelta = -deltaX * 0.002 * config.mouseSensitivity;
                const pitchDelta = -deltaY * 0.002 * config.mouseSensitivity;
                
                let newHeading = this.viewer.camera.heading + headingDelta;
                let newPitch = this.viewer.camera.pitch + pitchDelta;
                
                // 限制俯仰角范围 (-85° 到 +85°)
                const maxPitch = Cesium.Math.toRadians(85);
                const minPitch = Cesium.Math.toRadians(-85);
                newPitch = Cesium.Math.clamp(newPitch, minPitch, maxPitch);
                
                // 获取当前相机位置
                const position = this.viewer.camera.positionCartographic;
                
                // 计算新的相机视角
                this.viewer.camera.setView({
                    destination: Cesium.Cartesian3.fromDegrees(
                        Cesium.Math.toDegrees(position.longitude),
                        Cesium.Math.toDegrees(position.latitude),
                        position.height
                    ),
                    orientation: {
                        heading: newHeading,
                        pitch: newPitch,
                        roll: this.viewer.camera.roll
                    }
                });
                
            } catch (e) {
                console.warn("旋转相机失败:", e);
            }
        }

// 新增惯性动画方法
startInertiaAnimation() {
    const animate = () => {
        // 应用速度衰减
        this.inertia.velocityX *= this.inertia.friction;
        this.inertia.velocityY *= this.inertia.friction;
        
        // 如果速度足够小,停止动画
        if (Math.abs(this.inertia.velocityX) < 0.01 && Math.abs(this.inertia.velocityY) < 0.01) {
            return;
        }
        
        // 计算基于时间的移动量
        const currentTime = performance.now();
        const timeDelta = currentTime - this.inertia.lastTime;
        this.inertia.lastTime = currentTime;
        
        const deltaX = this.inertia.velocityX * timeDelta;
        const deltaY = this.inertia.velocityY * timeDelta;
        
        this.rotateCamera(deltaX, deltaY);
        
        // 继续动画
        this.inertia.animationFrame = requestAnimationFrame(animate);
    };
    
    this.inertia.lastTime = performance.now();
    this.inertia.animationFrame = requestAnimationFrame(animate);
}

        toggleRotation() {
            this.isRotating ? this.stopRotation() : this.startRotation();
            this.rotateToggleBtn.textContent = this.isRotating ? "停止旋转" : "开启旋转";
            this.rotateToggleBtn.classList.toggle('active');
            this.compass.classList.toggle('auto-rotate', this.isRotating);
        }
        
        startRotation() {
            if (!this.tileset || this.isRotating) return;
            
            this.isRotating = true;
            this.rotateToggleBtn.textContent = "停止旋转";
            this.rotateToggleBtn.classList.add('active');
            
            const boundingSphere = this.tileset.boundingSphere;
            const center = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
                boundingSphere.center
            );
            
            this.rotateHandler = this.viewer.clock.onTick.addEventListener(() => {
                if (this.isRotating && center) {
                    const angle = Cesium.Math.toRadians(0.3 * this.rotationSpeed);
                    this.viewer.camera.rotate(center, angle);
                }
            });
            
            this.compass.classList.add('auto-rotate');
        }
        
        stopRotation() {
            if (!this.isRotating) return;
            
            this.isRotating = false;
            this.rotateToggleBtn.textContent = "开启旋转";
            this.rotateToggleBtn.classList.remove('active');
            
            if (this.rotateHandler) {
                this.viewer.clock.onTick.removeEventListener(this.rotateHandler);
                this.rotateHandler = null;
            }
            
            this.compass.classList.remove('auto-rotate');
        }
        
        resetView() {
            this.stopRotation();
            
            if (this.initialView) {
                this.viewer.camera.flyTo({
                    destination: this.initialView.position,
                    orientation: {
                        heading: this.initialView.heading,
                        pitch: this.initialView.pitch,
                        roll: this.initialView.roll
                    },
                    duration: 1.0
                });
            }
        }

        updateStatus(message, progress) {
            this.statusEl.textContent = message;
            if (progress !== undefined) {
                this.progressEl.style.width = `${progress}%`;
            }
            
            if (config.showDebug) {
                this.debugEl.innerHTML = `
                    
状态: ${message}
进度: ${progress || 0}%
3D Tiles: ${this.tileset ? '已加载' : '未加载'}
`
; this.debugEl.style.display = 'block'; } } completeLoading() { this.updateStatus('加载完成', 100); setTimeout(() => { this.loadingEl.style.opacity = '0'; setTimeout(() => { this.loadingEl.style.display = 'none'; }, 500); }, 1000); } handleError(error) { console.error('加载失败:', error); this.statusEl.innerHTML = `
错误: ${error.message}
建议操作:
  • 检查服务器是否运行
  • 确认CORS配置正确
  • 查看控制台获取详细信息
`
; this.progressEl.style.background = '#ff5252'; const retryBtn = document.createElement('button'); retryBtn.textContent = '重试加载'; retryBtn.style.marginTop = '15px'; retryBtn.style.padding = '8px 16px'; retryBtn.style.borderRadius = '4px'; retryBtn.style.border = 'none'; retryBtn.style.background = '#4CAF50'; retryBtn.style.color = 'white'; retryBtn.style.cursor = 'pointer'; retryBtn.onclick = () => window.location.reload(); this.statusEl.appendChild(retryBtn); if (config.showDebug) { this.debugEl.innerHTML += `
错误详情: ${error.message}
堆栈: ${error.stack || '无堆栈信息'}
`
; this.debugEl.style.display = 'block'; } } } document.addEventListener('DOMContentLoaded', () => { new CesiumViewer(); });
script> body> html>

效果如下:Win11通过Nginx启动3DTiles,通过html加载3DTiles详细操作流程_第1张图片

详细代码及组件已上传

你可能感兴趣的:(nginx,3d,html)