我正在学习Three.js,并且正在研究太阳系的模型以了解其工作原理。所以我有一个场景,地球围绕太阳旋转,月亮围绕地球旋转。
现在,我想专注于月球并使用控件围绕月球旋转(同时将其始终保持在屏幕中央)。 OrbitControls似乎是最理想的选择,但我无法让它们与移动的Moon配合使用。
这是我的3次尝试(请忽略地球和月球是立方体)。
首先,我创建了一个场景,其中camera
是月球的孩子(没有OrbitControls)。
moon.add(camera);
camera.lookAt(0, 0, 0);
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
结果:它很好地将月球放在了中心,但是显然您无法导航场景,因为我还没有聘用OrbitControls
。但这只是参考。
然后我添加了OrbitControls
。
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
结果:月球已从中心移到侧面(不知道为什么吗?)。当您开始使用鼠标导航时,一切都会变得疯狂。效果就像OrbitControls
围绕场景中心导航,而相机围绕其父对象(月亮)导航。实际上,它们不会以一致的方式更改状态,并且一切都变得疯狂。
我尝试的最后一个选择是强行设置controls.target
,使其始终指向月球。因为月亮不断地移动,所以我必须在每次渲染之前完成。
const p = new THREE.Vector3();
const q = new THREE.Quaternion();
const s = new THREE.Vector3();
moon.matrixWorld.decompose(p, q, s);
// now setting controls target to Moon's position (in scene's coordinates)
controls.target.copy(p);
render();
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
const p = new THREE.Vector3();
const q = new THREE.Quaternion();
const s = new THREE.Vector3();
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
moon.matrixWorld.decompose(p, q, s);
controls.target.copy(p);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
结果:最初,月亮位于屏幕的侧面(与第二次尝试的位置相同),但是随后当您开始导航时,月亮“跳”到屏幕的中心屏幕上,您可以浏览它。几乎完美。只要您不缩放即可。放大/缩小时,您开始看到月亮在缩放过程中旋转。
OrbitControls
不尊重相机的父母是月球的事实,并且一直在场景中心附近导航?OrbitControls
之后,月亮会“跳到”屏幕的侧面?target
循环跟随月球既不优雅也不起作用)?r。 98
编辑:编辑性修改使句子更清晰。
答案 0 :(得分:3)
我通过引入伪造的相机使其工作,除了camera.parent
fakeCamera = camera.clone(); // parent becomes null
controls = new THREE.OrbitControls(fakeCamera, renderer.domElement);
通过这种方式OrbitControls
的摄像机具有自己的坐标系。
然后,在渲染之前,我将fakeCamera
的值复制回用于渲染的真实相机。
camera.position.copy(fakeCamera.position);
camera.quaternion.copy(fakeCamera.quaternion);
camera.scale.copy(fakeCamera.scale);
render();
效果很好。
编辑
我注意到
camera.position.copy(fakeCamera.position);
camera.quaternion.copy(fakeCamera.quaternion);
camera.scale.copy(fakeCamera.scale);
可以替换为
camera.copy(fakeCamera);
(下面的代码已相应更新)
var camera, fakeCamera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
fakeCamera = camera.clone();
controls = new THREE.OrbitControls(fakeCamera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
camera.copy(fakeCamera);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/98/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
答案 1 :(得分:0)
我认为您的解决方法是一个不错的解决方案,因为它不需要修改导入的代码。另外,只要不用于渲染,额外的相机维护成本就不高。基于相同的原理,这是一个可以应用的OrbitControls子类。请注意,localTarget
属性只是target
属性的别名。没有globalTarget
属性。
THREE.OrbitControlsLocal = function ( realObject, domElement ) {
this.realObject = realObject;
//Camera and Object3D have different forward direction:
let placeholderObject = realObject.isCamera ? new THREE.PerspectiveCamera() : new THREE.Object3D;
this.placeholderObject = placeholderObject;
THREE.OrbitControls.call( this, placeholderObject, domElement );
let globalUpdate = this.update;
this.globalUpdate = globalUpdate;
this.update = function() {
//This responds to changes made to realObject from outside the controls:
placeholderObject.position.copy( realObject.position );
placeholderObject.quaternion.copy( realObject.quaternion);
placeholderObject.scale.copy( realObject.scale );
placeholderObject.up.copy( realObject.up );
var retval = globalUpdate();
realObject.position.copy( placeholderObject.position );
realObject.quaternion.copy( placeholderObject.quaternion);
realObject.scale.copy( placeholderObject.scale );
return retval ;
};
this.update();
};
THREE.OrbitControlsLocal.prototype = Object.create(THREE.OrbitControls.prototype);
THREE.OrbitControlsLocal.prototype.constructor = THREE.OrbitControlsLocal;
Object.defineProperties(THREE.OrbitControlsLocal.prototype, {
localTarget: {
get: ()=>this.target,
set: v=>this.target=v
}
});
我以前的解决方案,在应用lookAt之前仅将本地目标转换为世界空间是不正确的。问题似乎在于,每次更新时lookAt都会根据其世界空间向上方向(camera.up或object.up)对照相机进行定向。占位符/ fakeCamera解决方案不存在此问题。 (请参见PR https://github.com/mrdoob/three.js/pull/16374)