使用的库:aframe-v0.9.2、aframe-extras.min.js、aframe-physics-system.min.js、CANNON.js(cannon.js前面物理框架引入了不需要再次引入)
说明点:aframe其实是用了cannon.js来做物理引擎的,因为aframe很多东西都标签化了,代码不方便提取,所以不太方便直接使用cannon的写法,这里急需要曲线救国了。
一般的情况分为两种:
第一种:
手动加入a-box等,使用标签来创建元素
第二种:
使用a-obj-model,通过导入模型来实现
下面分开来讲实现
第一种:使用a-box等基础元素直接构建
<html>
<head>
<script src="aframe.min.js"></script>
<script src="aframe-physics-system.min.js"></script>
</head>
<body>
<a-scene physics>
<a-box position="-1 4 -3" rotation="0 45 0" color="#4CC3D9" dynamic-body></a-box>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
第二种:
导入模型有两种做法,一个是代码处理,另外一个就是人力了
代码处理的思路:把模型里面的mesh变成一个个的a-box然后隐藏起来,变成不可见的entity,,记得墙面模型也需要引入显示的,这样就能直接使用框架本身了,避免很多麻烦。这里需要摄像机有kinema-body组件,就先在开头定义一哈。详细的逻辑暂时不优化,最后的drawWall()里面一定能看得懂。
人力的思路:在代码里面自己写a-box然后手动去ctrl+i的模式里面一个一个调整大小和位置,和墙面吻合,这个就很累了,所以推荐代码解决。
<!DOCTYPE html>
<html>
<head>
<script src="aframe.min.js"></script>
<script src="aframe-extras.min.js"></script>
<script src="aframe-physics-system.min.js"></script>
<!-- <script src="//cdn.rawgit.com/donmccurdy/aframe-physics-system/v3.3.0/dist/aframe-physics-system.min.js"></script> -->
<script>
/**
* Kinema body.
*
* Based on kinematic-body, from AFrame Extras (for now, basically a copy,
* just because I read it is deprecated in AFrame Extras)
*
* https://github.com/donmccurdy/aframe-extras/blob/master/src/misc/kinematic-body.js
*
* Managed dynamic body, which moves but is not affected (directly) by the
* physics engine. This is not a true kinematic body, in the sense that we are
* letting the physics engine _compute_ collisions against it and selectively
* applying those collisions to the object. The physics engine does not decide
* the position/velocity/rotation of the element.
*
* Used for the camera object, because full physics simulation would create
* movement that feels unnatural to the player. Bipedal movement does not
* translate nicely to rigid body physics.
*
* See: http://www.learn-cocos2d.com/2013/08/physics-engine-platformer-terrible-idea/
* And: http://oxleygamedev.blogspot.com/2011/04/player-physics-part-2.html
*/
const EPS = 0.000001;
AFRAME.registerComponent('kinema-body', {
dependencies: ['velocity'],
/*******************************************************************
* Schema
*/
schema: {
mass: {
default: 5
},
radius: {
default: 1.3
},
linearDamping: {
default: 0.05
},
enableSlopes: {
default: true
},
enableJumps: {
default: false
},
},
/*******************************************************************
* Lifecycle
*/
init: function() {
this.system = this.el.sceneEl.systems.physics;
this.system.addComponent(this);
const el = this.el,
data = this.data,
position = (new CANNON.Vec3()).copy(el.object3D.getWorldPosition(new THREE.Vector3()));
this.body = new CANNON.Body({
material: this.system.getMaterial('staticMaterial'),
position: position,
mass: data.mass,
linearDamping: data.linearDamping,
fixedRotation: true
});
this.body.addShape(
new CANNON.Sphere(data.radius),
new CANNON.Vec3(0, data.radius, 0)
);
this.body.el = this.el;
this.el.body = this.body;
this.system.addBody(this.body);
if (el.hasAttribute('wasd-controls')) {
console.warn('[kinema-body] Not compatible with wasd-controls, use movement-controls.');
}
},
remove: function() {
this.system.removeBody(this.body);
this.system.removeComponent(this);
delete this.el.body;
},
/*******************************************************************
* Update
*/
/**
* Checks CANNON.World for collisions and attempts to apply them to the
* element automatically, in a player-friendly way.
*
* There's extra logic for horizontal surfaces here. The basic requirements:
* (1) Only apply gravity when not in contact with _any_ horizontal surface.
* (2) When moving, project the velocity against exactly one ground surface.
* If in contact with two ground surfaces (e.g. ground + ramp), choose
* the one that collides with current velocity, if any.
*/
beforeStep: function(t, dt) {
if (!dt) return;
const el = this.el;
const data = this.data
const body = this.body;
if (!data.enableJumps) body.velocity.set(0, 0, 0);
body.position.copy(el.getAttribute('position'));
},
step: (function() {
const velocity = new THREE.Vector3(),
normalizedVelocity = new THREE.Vector3(),
currentSurfaceNormal = new THREE.Vector3(),
groundNormal = new THREE.Vector3();
return function(t, dt) {
if (!dt) return;
let body = this.body,
data = this.data,
didCollide = false,
height, groundHeight = -Infinity,
groundBody,
contacts = this.system.getContacts();
dt = Math.min(dt, this.system.data.maxInterval * 1000);
groundNormal.set(0, 0, 0);
velocity.copy(this.el.getAttribute('velocity'));
body.velocity.copy(velocity);
for (var i = 0, contact; contact = contacts[i]; i++) {
// 1. Find any collisions involving this element. Get the contact
// normal, and make sure it's oriented _out_ of the other object and
// enabled (body.collisionReponse is true for both bodies)
if (!contact.enabled) {
continue;
}
if (body.id === contact.bi.id) {
contact.ni.negate(currentSurfaceNormal);
} else if (body.id === contact.bj.id) {
currentSurfaceNormal.copy(contact.ni);
} else {
continue;
}
didCollide = body.velocity.dot(currentSurfaceNormal) < -EPS;
if (didCollide && currentSurfaceNormal.y <= 0.5) {
// 2. If current trajectory attempts to move _through_ another
// object, project the velocity against the collision plane to
// prevent passing through.
velocity.projectOnPlane(currentSurfaceNormal);
} else if (currentSurfaceNormal.y > 0.5) {
// 3. If in contact with something roughly horizontal (+/- 45º) then
// consider that the current ground. Only the highest qualifying
// ground is retained.
height = body.id === contact.bi.id ?
Math.abs(contact.rj.y + contact.bj.position.y) :
Math.abs(contact.ri.y + contact.bi.position.y);
if (height > groundHeight) {
groundHeight = height;
groundNormal.copy(currentSurfaceNormal);
groundBody = body.id === contact.bi.id ? contact.bj : contact.bi;
}
}
}
normalizedVelocity.copy(velocity).normalize();
if (groundBody && (!data.enableJumps || normalizedVelocity.y < 0.5)) {
if (!data.enableSlopes) {
groundNormal.set(0, 1, 0);
} else if (groundNormal.y < 1 - EPS) {
groundNormal.copy(this.raycastToGround(groundBody, groundNormal));
}
// 4. Project trajectory onto the top-most ground object, unless
// trajectory is > 45º.
velocity.projectOnPlane(groundNormal);
} else if (this.system.driver.world) {
// 5. If not in contact with anything horizontal, apply world gravity.
// TODO - Why is the 4x scalar necessary.
// NOTE: Does not work if physics runs on a worker.
velocity.add(this.system.driver.world.gravity.scale(dt * 4.0 / 1000));
}
body.velocity.copy(velocity);
this.el.setAttribute('velocity', body.velocity);
this.el.setAttribute('position', body.position);
};
}()),
/**
* When walking on complex surfaces (trimeshes, borders between two shapes),
* the collision normals returned for the player sphere can be very
* inconsistent. To address this, raycast straight down, find the collision
* normal, and return whichever normal is more vertical.
* @param {CANNON.Body} groundBody
* @param {CANNON.Vec3} groundNormal
* @return {CANNON.Vec3}
*/
raycastToGround: function(groundBody, groundNormal) {
let ray,
hitNormal,
vFrom = this.body.position,
vTo = this.body.position.clone();
ray = new CANNON.Ray(vFrom, vTo);
ray._updateDirection(); // TODO - Report bug.
ray.intersectBody(groundBody);
if (!ray.hasHit) return groundNormal;
// Compare ABS, in case we're projecting against the inside of the face.
hitNormal = ray.result.hitNormalWorld;
return Math.abs(hitNormal.y) > Math.abs(groundNormal.y) ? hitNormal : groundNormal;
}
});
</script>
<body>
<a-scene physics="debug: false;" vr-mode-ui="enabled: false" antialias="true">
<a-assets>
<a-asset-item id="wall3-obj" src="wall3.obj"></a-asset-item>
<a-asset-item id="0-mtl" src="0.mtl"></a-asset-item>
<img id="wood5" src="k.png">
<a-asset-item id="sopraporta-obj" src="sopraporta.obj"></a-asset-item>
<a-asset-item id="0-mtl" src="0.mtl"></a-asset-item>
<img id="wood5" src="k.png">
<a-asset-item id="jgz-obj" src="jgz.obj"></a-asset-item>
<a-asset-item id="jgz-mtl" src="jgz.mtl"></a-asset-item>
<img id="wood111" src="d.png">
<a-asset-item id="wljg-obj" src="wljg.obj"></a-asset-item>
<a-asset-item id="wljg-mtl" src="wljg.mtl"></a-asset-item>
<img id="wood111" src="d.png">
<a-asset-item id="zw2-obj" src="zw2.obj"></a-asset-item>
<a-asset-item id="zw2-mtl" src="zw2.mtl"></a-asset-item>
<img id="wood111" src="d.png">
<a-asset-item id="wire frame2-obj" src="wire frame2.obj"></a-asset-item>
<img id="wood1" src="kk.jpg">
<a-asset-item id="logo-obj" src="logo.obj"></a-asset-item>
<a-asset-item id="logo-mtl" src="logo.mtl"></a-asset-item>
<img id="wood1" src="kk.jpg">
<a-asset-item id="sopraporta wire-obj" src="sopraporta wire.obj"></a-asset-item>
<a-asset-item id="sopraporta wire" src="sopraporta wire.mtl"></a-asset-item>
<img id="wood1" src="kk.jpg">
<a-asset-item id="name baseplate-obj" src="name baseplate.obj"></a-asset-item>
<a-asset-item id="name baseplate" src="name baseplate.mtl"></a-asset-item>
<img id="wood1" src="kk.jpg">
<a-asset-item id="walls-dae" src="wall3.dae"></a-asset-item>
<a-asset-item id="walls-glb" src="wall3.glb"></a-asset-item>
</a-assets>
<!-- <a-entity id="wall_obj" obj-model="obj: url(wall3.obj)" material="src: #wood5; repeat: 1 1.0" opacity="0.4" scale="0.01 0.01 0.01"
position="0 0 0" static-body></a-entity> -->
<!-- <a-obj-model id="wall_obj" src="#wall3-obj" material="src: #wood5; repeat: 1 1.0" opacity="0.4" scale="0.01 0.01 0.01"
position="0 0 0" visible="false"></a-obj-model> -->
<!-- <a-box id="box" static-body position="0 0 0" height="3" width="4" color="red"></a-box> -->
<a-sky color="#ECECEC"></a-sky>
<a-entity kinema-body="mass:1;radius:0.3;" movement-controls="fly: false;speed:0.2;" position="0 0 4">
<a-entity camera position="0 1 0" look-controls="reverseMouseDrag:false;pointerLockEnabled:true;">
<a-entity cursor
position="0 0 -0.1"
geometry="primitive: ring; radiusInner: 0.001; radiusOuter: 0.0015"
material="color: black; shader: flat">
</a-entity>
</a-entity>
<!-- <a-camera id="c1" look-controls="enabled:true;reverseMouseDrag:true;reverseTouchDrag:false;" zoom="1"></a-camera> -->
</a-entity>
<a-plane height="100" width="100" rotation="-90 0 0" src="bd.jpg" static-body></a-plane>
<a-obj-model id="wall_obj" src="#wall3-obj" material="src: #wood5; repeat: 1 1.0" opacity="0.4" scale="0.01 0.01 0.01" position="0 0 0" class="clickable"></a-obj-model>
<a-obj-model src="#wire frame2-obj" scale="0.01 0.015 0.01" opacity="0.99" position="0 0.01 0" color="#21c7e1" class="clickable"></a-obj-model>
<!-- <a-entity id="wall_obj" gltf-model="#walls-glb" scale="0.01 0.01 0.01" position="0 0 0" visible="true"></a-entity> -->
</a-scene>
</body>
<script>
var sceneEl = document.querySelector('a-scene');
var wall = document.querySelector('#wall_obj');
wall.addEventListener('model-loaded', function(e) {
drawWall(wall);
});
function drawWall(wall) {
wall_mesh = wall.object3D.children[0].children;
for (var i = 0; i < wall_mesh.length; i++) {
if (wall_mesh[i].isMesh) {
var object = wall_mesh[i];
var geometry = object.geometry;
var geometry_s_p = createBoundingBoxShape(object);
var box_size = geometry_s_p.box;
var scale = wall.object3D.scale;
var depth = "2",
height = "4",
width = "0.5";
width = (box_size.max.x - box_size.min.x) * scale.x;
height = (box_size.max.y - box_size.min.y) * scale.y * 2;
depth = (box_size.max.z - box_size.min.z) * scale.z;
var position = {
x: (box_size.min.x + (width / 2)) * scale.x,
y: (box_size.min.y + (height / 2)) * scale.y,
z: (box_size.min.z + (depth / 2)) * scale.z
};
position = geometry_s_p.position;
position = {
x: position.x * scale.x,
y: position.y * scale.y,
z: position.z * scale.z
};
//创建entity
var boxEl = document.createElement('a-box');
boxEl.setAttribute('material', {
color: '#ffffff'
});
boxEl.setAttribute('width', width);
boxEl.setAttribute('height', height);
boxEl.setAttribute('depth', depth);
boxEl.setAttribute('position', position);
boxEl.setAttribute('static-body', {mass:1});
// boxEl.setAttribute('scale', scale);
boxEl.setAttribute('visible', false);
sceneEl.appendChild(boxEl);
}
}
}
/**
* Bounding box needs to be computed with the entire mesh, not just geometry.
* @param {THREE.Object3D} mesh
* @return {CANNON.Shape}
*/
function createBoundingBoxShape(object) {
var shape, localPosition, worldPosition,
box = new THREE.Box3();
box.setFromObject(object);
if (!isFinite(box.min.lengthSq())) return null;
shape = new CANNON.Box(new CANNON.Vec3(
(box.max.x - box.min.x) / 2,
(box.max.y - box.min.y) / 2,
(box.max.z - box.min.z) / 2
));
object.updateMatrixWorld();
worldPosition = new THREE.Vector3();
worldPosition.setFromMatrixPosition(object.matrixWorld);
localPosition = box.translate(worldPosition.negate()).getCenter();
if (localPosition.lengthSq()) {
shape.offset = localPosition;
}
return {box:box, position:localPosition};
}
</script>
</html>
下载地址:https://download.csdn.net/download/lz610756247/11868604