aframe摄像机穿墙问题-解决思路

    使用的库: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.youkuaiyun.com/download/lz610756247/11868604

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值