防止相机进入 'through' 帧对象
Preventing camera from going 'through' aframe object
假设我有一个圆柱体模型,我将其加载到我的 webvr 场景中。我怎样才能使物体坚固? IE。用户(相机视图)不能在对象内占据任何位置或移动 'through' 对象。我怎样才能做到这一点?
<a-scene>
<a-assets>
<a-asset-item id="cube-obj" src="cube.obj"></a-asset-item>
</a-assets>
<a-entity id="cameraWrapper" position="0 2 10" rotation="0 0 0">
<a-camera near="0.1" user-height="0" id="camera" listener></a-camera>
</a-entity>
<a-entity obj-model="obj: #cube-obj; mtl: #cube-mtl" scale="1 1 1" rotation="-90 0 0"></a-entity>
<a-plane position="0 4 4" rotation="-90 0 -90" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
这取决于您计划支持哪些设备,以及您如何允许用户
浏览你的场景。对于大多数 VR 体验,遵循最佳实践并且仅
根据用户的动作按比例移动相机。如果用户步骤
在房间尺度 space 中向前并且相机是 "blocked," 这是非常糟糕的
经验。对于大多数 VR 应用程序,最好使用
teleportation,
设计场景以避开障碍物,或探索更多创意
在世界各地移动用户的方式。
对于使用游戏手柄或 WASD 控件的非 VR 桌面体验,或者对于 VR 场景
如果摄像头在车内,您可以添加一个
physics engine到
防止移动通过障碍物。
^我会尽快将其添加到 A-Frame 常见问题解答或文档中。这已添加到 A-Frame FAQ. Here's an example using checkpoints, and an example using a physics engine.
因为我来到这里是为了寻找更简单的答案(不是处理自定义的,而是处理标准的 AFrame 对象),所以这可能不是您想要的。但是,如果您只是要求 "preventing the camera going through AFrame objects",也许这会对您有所帮助。
在下面的示例中,我使用 AFrame Physics System 来管理物理。
我从 AFrame Extras 中复制了 kinematic-body
组件,名称为 kinema-body
(参见嵌入式脚本)只是因为它被标记为 "deprecated",所以它可能会在未来消失.
您将看到如何在场景中移动,但是当到达一个对象时,您无法穿过它。这对我在桌面和 Oculus Go 中都适用。技巧很简单:相机体现在 kinema-body
装备中,物体(平面和盒子)是 static-body
。当 kinema-body
触及 static-body
时,它会停止(好吧,或者试图绕着它移动,具体取决于它的移动方式)。
<!DOCTYPE html>
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v5.0.0/dist/aframe-extras.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: true">
<a-box static-body position="0 0 0" height="3" width="4" color="red"></a-box>
<a-plane static-body position="0 0 0" rotation="-90 0 0" width="8" height="14" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
<a-entity kinema-body="radius: 0.8" movement-controls="fly: false" position="0 0 4" look-controls>
<a-entity camera position="0 1.6 0" ></a-entity>
</a-entity>
</a-scene>
</body>
</html>
假设我有一个圆柱体模型,我将其加载到我的 webvr 场景中。我怎样才能使物体坚固? IE。用户(相机视图)不能在对象内占据任何位置或移动 'through' 对象。我怎样才能做到这一点?
<a-scene>
<a-assets>
<a-asset-item id="cube-obj" src="cube.obj"></a-asset-item>
</a-assets>
<a-entity id="cameraWrapper" position="0 2 10" rotation="0 0 0">
<a-camera near="0.1" user-height="0" id="camera" listener></a-camera>
</a-entity>
<a-entity obj-model="obj: #cube-obj; mtl: #cube-mtl" scale="1 1 1" rotation="-90 0 0"></a-entity>
<a-plane position="0 4 4" rotation="-90 0 -90" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
这取决于您计划支持哪些设备,以及您如何允许用户 浏览你的场景。对于大多数 VR 体验,遵循最佳实践并且仅 根据用户的动作按比例移动相机。如果用户步骤 在房间尺度 space 中向前并且相机是 "blocked," 这是非常糟糕的 经验。对于大多数 VR 应用程序,最好使用 teleportation, 设计场景以避开障碍物,或探索更多创意 在世界各地移动用户的方式。
对于使用游戏手柄或 WASD 控件的非 VR 桌面体验,或者对于 VR 场景 如果摄像头在车内,您可以添加一个 physics engine到 防止移动通过障碍物。
^我会尽快将其添加到 A-Frame 常见问题解答或文档中。这已添加到 A-Frame FAQ. Here's an example using checkpoints, and an example using a physics engine.
因为我来到这里是为了寻找更简单的答案(不是处理自定义的,而是处理标准的 AFrame 对象),所以这可能不是您想要的。但是,如果您只是要求 "preventing the camera going through AFrame objects",也许这会对您有所帮助。
在下面的示例中,我使用 AFrame Physics System 来管理物理。
我从 AFrame Extras 中复制了 kinematic-body
组件,名称为 kinema-body
(参见嵌入式脚本)只是因为它被标记为 "deprecated",所以它可能会在未来消失.
您将看到如何在场景中移动,但是当到达一个对象时,您无法穿过它。这对我在桌面和 Oculus Go 中都适用。技巧很简单:相机体现在 kinema-body
装备中,物体(平面和盒子)是 static-body
。当 kinema-body
触及 static-body
时,它会停止(好吧,或者试图绕着它移动,具体取决于它的移动方式)。
<!DOCTYPE html>
<html>
<head>
<script src="//aframe.io/releases/0.8.2/aframe.min.js"></script>
<script src="//cdn.rawgit.com/donmccurdy/aframe-extras/v5.0.0/dist/aframe-extras.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: true">
<a-box static-body position="0 0 0" height="3" width="4" color="red"></a-box>
<a-plane static-body position="0 0 0" rotation="-90 0 0" width="8" height="14" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
<a-entity kinema-body="radius: 0.8" movement-controls="fly: false" position="0 0 4" look-controls>
<a-entity camera position="0 1.6 0" ></a-entity>
</a-entity>
</a-scene>
</body>
</html>