new Ammo.btHeightfieldTerrainShape 创建一个高度场地形的物理形状对象。

demo案例

在这里插入图片描述

new Ammo.btHeightfieldTerrainShape 创建一个高度场地形的物理形状对象。

构造函数参数:

  • width: 高度场的宽度(整数)
  • depth: 高度场的深度(整数)
  • heightData: 包含高度数据的内存块的指针
  • heightScale: 高度数据的缩放比例
  • minHeight: 高度场的最小高度
  • maxHeight: 高度场的最大高度
  • upAxis: 高度场的上轴方向(0表示X轴,1表示Y轴,2表示Z轴)
  • heightDataType: 高度数据的类型(字符串,例如 'PHY_FLOAT'
  • flipQuadEdges: 是否翻转四边形边缘的标志(布尔值)

方法和属性:

  • setMargin(margin): 设置高度场形状的边缘间距
  • setLocalScaling(scale): 设置高度场形状的本地缩放
  • getHeight: 获取指定位置的高度值
  • getMinHeight: 获取高度场的最小高度
  • getMaxHeight: 获取高度场的最大高度
  • upAxis: 高度场的上轴方向
  • heightDataType: 高度数据的类型
  • flipQuadEdges: 是否翻转四边形边缘的标志

注意事项:

  • heightData应该是一个指向包含高度数据的内存块的指针,格式化为指定的高度数据类型。
  • 在使用之前,应确保使用了Ammo.js的内存分配器来为heightData分配内存,并在不再需要时正确释放内存。

demo 源码

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Ammo.js terrain heightfield demo</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <!-- 引入主要的样式文件 -->
    <link type="text/css" rel="stylesheet" href="main.css">
    <style>
        body {
            color: #333;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <div id="info">Ammo.js physics terrain heightfield demo</div>

    <!-- 引入Ammo.js -->
    <script src="jsm/libs/ammo.wasm.js"></script>

    <!-- 导入映射关系 -->
    <script type="importmap">
        {
            "imports": {
                "three": "../build/three.module.js",
                "three/addons/": "./jsm/"
            }
        }
    </script>

    <!-- 使用ES6模块 -->
    <script type="module">
        // 导入Three.js库
        import * as THREE from 'three';
        // 导入性能统计模块
        import Stats from 'three/addons/libs/stats.module.js';
        // 导入轨道控制器
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        // 地形参数
        const terrainWidthExtents = 100;
        const terrainDepthExtents = 100;
        const terrainWidth = 128;
        const terrainDepth = 128;
        const terrainHalfWidth = terrainWidth / 2;
        const terrainHalfDepth = terrainDepth / 2;
        const terrainMaxHeight = 8;
        const terrainMinHeight = - 2;

        let container, stats;
        let camera, scene, renderer;
        let terrainMesh;
        const clock = new THREE.Clock();

        // 物理变量
        let collisionConfiguration;
        let dispatcher;
        let broadphase;
        let solver;
        let physicsWorld;
        const dynamicObjects = [];
        let transformAux1;

        let heightData = null;
        let ammoHeightData = null;

        let time = 0;
        const objectTimePeriod = 3;
        let timeNextSpawn = time + objectTimePeriod;
        const maxNumObjects = 30;

        // 加载Ammo.js
        Ammo().then( function ( AmmoLib ) {

            Ammo = AmmoLib;

            init();
            animate();

        } );

        // 初始化函数
        function init() {

            // 生成地形高度数据
            heightData = generateHeight( terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight );

            // 初始化图形界面
            initGraphics();

            // 初始化物理世界
            initPhysics();

        }

        // 初始化图形界面
        function initGraphics() {

            // 获取容器元素
            container = document.getElementById( 'container' );

            // 创建渲染器
            renderer = new THREE.WebGLRenderer( { antialias: true } );
            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.shadowMap.enabled = true;
            container.appendChild( renderer.domElement );

            // 创建性能统计器
            stats = new Stats();
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.top = '0px';
            container.appendChild( stats.domElement );

            // 创建相机
            camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );

            // 创建场景
            scene = new THREE.Scene();
            scene.background = new THREE.Color( 0xbfd1e5 );

            // 设置相机位置
            camera.position.y = heightData[ terrainHalfWidth + terrainHalfDepth * terrainWidth ] * ( terrainMaxHeight - terrainMinHeight ) + 5;
            camera.position.z = terrainDepthExtents / 2;
            camera.lookAt( 0, 0, 0 );

            // 创建轨道控制器
            const controls = new OrbitControls( camera, renderer.domElement );
            controls.enableZoom = false;

            // 创建地形几何体
            const geometry = new THREE.PlaneGeometry( terrainWidthExtents, terrainDepthExtents, terrainWidth - 1, terrainDepth - 1 );
            geometry.rotateX( - Math.PI / 2 );

            // 设置顶点高度
            const vertices = geometry.attributes.position.array;
            for ( let i = 0, j = 0, l = vertices.length; i < l; i ++, j += 3 ) {
                vertices[ j + 1 ] = heightData[ i ];
            }
            geometry.computeVertexNormals();

            // 创建地形网格
            const groundMaterial = new THREE.MeshPhongMaterial( { color: 0xC7C7C7 } );
            terrainMesh = new THREE.Mesh( geometry, groundMaterial );
            terrainMesh.receiveShadow = true;
            terrainMesh.castShadow = true;
            scene.add( terrainMesh );

            // 加载纹理
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load( 'textures/grid.png', function ( texture ) {
                texture.wrapS = THREE.RepeatWrapping;
                texture.wrapT = THREE.RepeatWrapping;
                texture.repeat.set( terrainWidth - 1, terrainDepth - 1 );
                groundMaterial.map = texture;
                groundMaterial.needsUpdate = true;
            } );

            // 添加环境光
            const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
            scene.add( ambientLight );

            // 添加平行光
            const light = new THREE.DirectionalLight( 0xffffff, 3 );
            light.position.set( 100, 100, 50 );
            light.castShadow = true;
            const dLight = 200;
            const sLight = dLight * 0.25;
            light.shadow.camera.left = - sLight;
            light.shadow.camera.right = sLight;
            light.shadow.camera.top = sLight;
            light.shadow.camera.bottom = - sLight;
            light.shadow.camera.near = dLight / 30;
            light.shadow.camera.far = dLight;
            light.shadow.mapSize.x = 1024 * 2;
            light.shadow.mapSize.y = 1024 * 2;
            scene.add( light );

            // 窗口大小改变事件监听器
            window.addEventListener( 'resize', onWindowResize );

        }

        // 窗口大小改变处理函数
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize( window.innerWidth, window.innerHeight );
        }

        // 初始化物理世界
        function initPhysics() {
            // 物理配置
            collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
            dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
            broadphase = new Ammo

.btDbvtBroadphase();
            solver = new Ammo.btSequentialImpulseConstraintSolver();
            physicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
            physicsWorld.setGravity( new Ammo.btVector3( 0, - 6, 0 ) );

            // 创建地形物体
            const groundShape = createTerrainShape();
            const groundTransform = new Ammo.btTransform();
            groundTransform.setIdentity();
            groundTransform.setOrigin( new Ammo.btVector3( 0, ( terrainMaxHeight + terrainMinHeight ) / 2, 0 ) );
            const groundMass = 0;
            const groundLocalInertia = new Ammo.btVector3( 0, 0, 0 );
            const groundMotionState = new Ammo.btDefaultMotionState( groundTransform );
            const groundBody = new Ammo.btRigidBody( new Ammo.btRigidBodyConstructionInfo( groundMass, groundMotionState, groundShape, groundLocalInertia ) );
            physicsWorld.addRigidBody( groundBody );

            transformAux1 = new Ammo.btTransform();
        }

        // 生成高度数据
        function generateHeight( width, depth, minHeight, maxHeight ) {
            const size = width * depth;
            const data = new Float32Array( size );
            const hRange = maxHeight - minHeight;
            const w2 = width / 2;
            const d2 = depth / 2;
            const phaseMult = 12;
            let p = 0;
            for ( let j = 0; j < depth; j ++ ) {
                for ( let i = 0; i < width; i ++ ) {
                    const radius = Math.sqrt(
                        Math.pow( ( i - w2 ) / w2, 2.0 ) +
                        Math.pow( ( j - d2 ) / d2, 2.0 ) );
                    const height = ( Math.sin( radius * phaseMult ) + 1 ) * 0.5 * hRange + minHeight;
                    data[ p ] = height;
                    p ++;
                }
            }
            return data;
        }

        // 创建地形形状
        function createTerrainShape() {
            const heightScale = 1;
            const upAxis = 1;
            const hdt = 'PHY_FLOAT';
            const flipQuadEdges = false;
            ammoHeightData = Ammo._malloc( 4 * terrainWidth * terrainDepth );
            let p = 0;
            let p2 = 0;
            for ( let j = 0; j < terrainDepth; j ++ ) {
                for ( let i = 0; i < terrainWidth; i ++ ) {
                    Ammo.HEAPF32[ ammoHeightData + p2 >> 2 ] = heightData[ p ];
                    p ++;
                    p2 += 4;
                }
            }
            const heightFieldShape = new Ammo.btHeightfieldTerrainShape(
                terrainWidth,
                terrainDepth,
                ammoHeightData,
                heightScale,
                terrainMinHeight,
                terrainMaxHeight,
                upAxis,
                hdt,
                flipQuadEdges
            );
            const scaleX = terrainWidthExtents / ( terrainWidth - 1 );
            const scaleZ = terrainDepthExtents / ( terrainDepth - 1 );
            heightFieldShape.setLocalScaling( new Ammo.btVector3( scaleX, 1, scaleZ ) );
            heightFieldShape.setMargin( 0.05 );
            return heightFieldShape;
        }

        
function generateObject() {
    // 生成随机的物体类型(1到4)
    const numTypes = 4;
    const objectType = Math.ceil( Math.random() * numTypes );

    let threeObject = null;
    let shape = null;

    const objectSize = 3;
    const margin = 0.05;

    let radius, height;

    switch ( objectType ) {
        // 生成球体
        case 1:
            radius = 1 + Math.random() * objectSize;
            threeObject = new THREE.Mesh( new THREE.SphereGeometry( radius, 20, 20 ), createObjectMaterial() );
            shape = new Ammo.btSphereShape( radius );
            shape.setMargin( margin );
            break;
        // 生成盒子
        case 2:
            const sx = 1 + Math.random() * objectSize;
            const sy = 1 + Math.random() * objectSize;
            const sz = 1 + Math.random() * objectSize;
            threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), createObjectMaterial() );
            shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
            shape.setMargin( margin );
            break;
        // 生成圆柱体
        case 3:
            radius = 1 + Math.random() * objectSize;
            height = 1 + Math.random() * objectSize;
            threeObject = new THREE.Mesh( new THREE.CylinderGeometry( radius, radius, height, 20, 1 ), createObjectMaterial() );
            shape = new Ammo.btCylinderShape( new Ammo.btVector3( radius, height * 0.5, radius ) );
            shape.setMargin( margin );
            break;
        // 默认生成圆锥体
        default:
            radius = 1 + Math.random() * objectSize;
            height = 2 + Math.random() * objectSize;
            threeObject = new THREE.Mesh( new THREE.ConeGeometry( radius, height, 20, 2 ), createObjectMaterial() );
            shape = new Ammo.btConeShape( radius, height );
            break;
    }

    // 设置物体的位置
    threeObject.position.set( ( Math.random() - 0.5 ) * terrainWidth * 0.6, terrainMaxHeight + objectSize + 2, ( Math.random() - 0.5 ) * terrainDepth * 0.6 );

    // 计算物体的质量和惯性
    const mass = objectSize * 5;
    const localInertia = new Ammo.btVector3( 0, 0, 0 );
    shape.calculateLocalInertia( mass, localInertia );

    // 创建物体的刚体
    const transform = new Ammo.btTransform();
    transform.setIdentity();
    const pos = threeObject.position;
    transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
    const motionState = new Ammo.btDefaultMotionState( transform );
    const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia );
    const body = new Ammo.btRigidBody( rbInfo );

    // 将刚体与物体关联
    threeObject.userData.physicsBody = body;
    threeObject.receiveShadow = true;
    threeObject.castShadow = true;

    // 将物体添加到场景中并保存到dynamicObjects数组中
    scene.add( threeObject );
    dynamicObjects.push( threeObject );
    physicsWorld.addRigidBody( body );
}

// 创建物体材质的函数
function createObjectMaterial() {
    const c = Math.floor( Math.random() * ( 1 << 24 ) );
    return new THREE.MeshPhongMaterial( { color: c } );
}

// 动画函数
function animate() {
    requestAnimationFrame( animate );
    render();
    stats.update();
}

// 渲染函数
function render() {
    const deltaTime = clock.getDelta();

    // 每隔一段时间生成一个物体
    if ( dynamicObjects.length < maxNumObjects && time > timeNextSpawn ) {
        generateObject();
        timeNextSpawn = time + objectTimePeriod;
    }

    // 更新物理世界
    updatePhysics( deltaTime );

    // 渲染场景
    renderer.render( scene, camera );

    time += deltaTime;
}

// 更新物理世界函数
function updatePhysics( deltaTime ) {
    physicsWorld.stepSimulation( deltaTime, 10 );

    // 更新物体位置和姿态
    for ( let i = 0, il = dynamicObjects.length; i < il; i ++ ) {
        const objThree = dynamicObjects[ i ];
        const objPhys = objThree.userData.physicsBody;
        const ms = objPhys.getMotionState();
        if ( ms ) {
            ms.getWorldTransform( transformAux1 );
            const p = transformAux1.getOrigin();
            const q = transformAux1.getRotation();
            objThree.position.set( p.x(), p.y(), p.z() );
            objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
        }
    }
}

    </script>
</body>
</html>

本内容来源于小豆包,想要更多内容请跳转小豆包 》

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值