我有一个加载在three.js画布中的框(collada文件)。我可以按预期与它互动。 但是,框大小因用户可以更改而异。
当我将它加载到500px乘500px画布时,如果框很大,用户必须在看到它之前放大,如果它很小,它很小,用户需要放大。尺寸根据不同而变化传递的变量。
如何在加载画布时使对象(collada文件)适合,然后让用户缩放?下面是加载点击以在three.js画布中显示3D对象的代码:
$scope.generate3D = function () {
// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png';
var boxDAE = baseBlobURL + 'Box.dae';
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;
// Update texture
newtexture = THREE.ImageUtils.loadTexture(texture0);
// Initial call to render scene, from this point, Orbit Controls render the scene per the event listener
THREE.DefaultLoadingManager.onProgress = function (item, loaded, total) {
// console.log( item, loaded, total ); // debug
if (loaded === total) render();
};
//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
});
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(100, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xdddddd);
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(500, 500);
// Load the box file
scene.add(box);
// Lighting
var light = new THREE.AmbientLight();
scene.add(light);
// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.noZoom = false;
controls.noPan = false;
// Add 3D rendering to HTML5 DOM element
var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);
}
// Render scene
function render() {
renderer.render(scene, camera);
console.log('loaded');
}
}
// Initial 3D Preview Load
$scope.generate3D();
更新:我已经评估了此处提供的解决方案:How to Fit Camera to Object但我不确定如何定义我的collada文件的距离,因为它可能会有所不同,具体取决于用户输入的尺寸。 collada文件是由用户向第三方供应商发送变量生成的,该第三方供应商返回随后加载到three.js中的collada文件。
更新2:感谢@ Blindman67,我更接近了解这是如何相互作用的。当我手动启动camera.position x,y,z值时,对象在屏幕上。我面临的挑战是如何确定每个盒子动态变化时正确的x,y,z值是多少,而且我确实有超过2.8亿的变化。我知道@ Blindman67已经在逻辑上给了我答案,但我只需要最后一步来发现如何为每次变化的对象找到正确的位置,这样我就可以设置正确的x,y,z。
答案 0 :(得分:8)
将3D对象适合视图
有几种方法可以将3d对象适合相机视图。
您可以打折最后一个选项,因为在大多数情况下它是不切实际的。 (虽然我注意到你在代码中这样做,因为在这个答案中有足够的信息可以解决如何缩放对象以适应。但我不建议你这样做)
因此,您可以将摄像机移入或移出,或保持静止并进行缩放。它与真实相机相同,可以缩放或靠近。
这两种方法都有利有弊。
<强>翻译强>
移动相机(小车)对于大多数情况来说是最好的,因为它保持透视相同(线条收敛到消失点的速度有多快),因此不会扭曲视野中的物体。在3D中,该方法存在3个问题。
<强>缩放强>
变焦涉及改变相机的焦距。在库中,您可以通过调整FOV(视野)来完成,这是以度为单位的视图左侧和右侧之间的角度。减小FOV有效地增加了焦距和放大(3D图形不具有像相机那样的焦距)。增加FOV缩小。这种方法存在问题。
使用什么方法是ip,你可以使用其中一种或两者结合使用。
如何完成
到手头的问题。我们需要知道对象在场景中的大小,并使用此信息将摄像机设置更改为所需的效果(即使对象适合显示)。
因此需要一些值。有关视觉说明,请参见图1。
您需要计算对象的边界球。或者使用近似于边界球的另一个值。我会把它留给你。
代码
var oL,cL; // for the math to make it readable
var FOV = 45 * (Math.PI / 180); // convert to radians
var objectLocation = oL = {x : 0, y : 0, z : 400};
var objectRadius = 50;
var cameraLocation = cL = {x : 0, y : 0, z : 0};
var farPlane = 1000;
var nearPlane = 200;
var displayWidth = 1600;
var displayHeight = 1000;
要确定边界球体在视图上的显示大小是简单的三角形。
// Get the distance from camera to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));Figure 1
当我们使用直角三角形(见图1)时,我们将结果乘以2得到总角度大小
// trig inverse tan of opposite over adjacent.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
获取对象占据的FOV的分数。
var objectView = objectAngularSize / FOV;
最后你得到了对象的像素大小。
var objectPixelSize = objectView * displayWidth;
这就是你要做的所有事情。这将有助于您了解是否使用上述代码和数学来尝试重新排列计算,以便通过移动相机或FOV使对象占据所需的像素大小。复制代码并不能教会你太多,通过应用它来使用上述信息会将它设置在你的脑海中,并且将来会使你需要的许多其他过程更容易。
那说复制代码是快速解决方案,是框架和库的全部内容。无需知道如何学习更好的东西。
缩放以适应。
这是最简单的并获得对象的角度大小并调整FOV以适应。 (注意我使用的是弧度.Tour.js使用你需要转换的FOV度数)
var requieredObjectPixelSize = 900;
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
// get the amount the FOV must be expanded by
var scaling = displayWidth / requieredObjectPixelSize;
// change the FOV to set the objects size
FOV = objectAngularSize * scaling;
将FOV转换为度数并使用它来创建相机。
翻译以适合
移动相机以适合物体。这是一个更复杂的方法,但是更好的方法。
// Approx size in pixels you want the object to occupy
var requieredObjectPixelSize = 900;
// camera distance to object
var distToObject = Math.sqrt(Math.pow(oL.x - cL.x, 2) + Math.pow(oL.y - cL.y, 2) + Math.pow(oL.z - cL.z, 2));
// get the object's angular size.
var objectAngularSize = Math.atan( (objectRadius) / distToObject ) * 2;
// get the fraction of the FOV the object must occupy to be 900 pixels
var scaling = requieredObjectPixelSize / displayWidth;
// get the angular size the object has to be
var objectAngularSize = FOV * scaling;
// use half the angular size to get the distance the camera must be from the object
distToObject = objectRadius / Math.tan(objectAngularSize / 2);
现在移动相机。它必须沿着物体和相机之间的矢量移动。
// Get the vector from the object to the camera
var toCam = {
x : cL.x - oL.x,
y : cL.y - oL.y,
z : cL.z - oL.z,
}
标准化矢量。这意味着使向量的长度等于1,并通过将每个分量(x,y,z)除以向量的长度来完成。
// First length
var len = Math.sqrt(Math.pow(toCam.x, 2) + Math.pow(toCam.y, 2) + Math.pow(toCam.z, 2));
// Then divide to normalise (you may want to test for divide by zero)
toCam.x /= len;
toCam.y /= len;
toCam.z /= len;
现在,您可以缩放矢量,使其等于摄像机距物体的距离。
toCam.x *= distToObject;
toCam.y *= distToObject;
toCam.z *= distToObject;
然后只需将矢量添加到对象的位置并将其放入相机位置
cL.x = oL.x + toCam.x;
cL.y = oL.y + toCam.y;
cL.z = oL.z + toCam.z;
cl
现在拥有相机位置。
最后一件事。您需要检查对象是否在视图内。
if (distToObject - objectRadius < nearPlane) {
nearPlane = (distToObject - objectRadius) * 0.8; // move the near plane towards the camera
// by 20% of the distance between the front of the object and the camera
}
if (distToObject + objectRadius > farPlane) {
farPlane = distToObject + objectRadius * 1.2; // move the far plane away from the camera
// by 1.2 time the object radius
}
还有一个问题。如果物体很小,物体可能非常靠近物体的前部位于相机后面。如果发生这种情况,您将需要使用缩放方法并将相机移回。这仅适用于非常精选的情况,大部分都可以忽略。
我没有提供有关如何将其与Three.js集成的信息,但这是一个适用于所有3D软件包的通用答案。您将不得不查阅three.js文档,了解如何更改各种相机设置。它很简单,适用于透视摄像机。
一个很好的答案,我需要忘记它,因为我没有看到错字和错误。我将在当天晚些时候返回并修复它。
希望有所帮助
答案 1 :(得分:2)
此代码将围绕给定对象“自动居中”相机。
对于用户'缩放',还要看一下three.js examples / js / controls / TrackballControls
。js,它们可以移动摄像机位置(并更改lookat和up)。如果您选择使用它,我记得您需要在使用centerCam
移动摄像机位置之前初始化控件。在这种情况下,centerCam
获取整个对象以准备用户交互,TrackballControls
侦听器在此之后接管。
<body onload="initPage()">
<canvas id="cadCanvas" width="400" height="300"></canvas>
<button onclick="loadFile()">Load Collada File</button>
</body>
function initPage(){
var domCanvas = document.getElementById('cadCanvas');
scene = new THREE.Scene();
var fovy = 75;
camera = new THREE.PerspectiveCamera( fovy, domCanvas.width/domCanvas.height, 0.1, 1000 );
renderer = new THREE.WebGLRenderer({canvas:domCanvas});
renderer.setSize( domCanvas.width, domCanvas.height );
}
function loadFile(){
new THREE.ColladaLoader().load( url, function(obj3D) {
scene.add(obj3D);
centerCam(obj3D);
renderer.render(scene, camera);
});
}
function centerCam(aroundObject3D){
//calc cam pos from Bounding Box
var BB = new THREE.Box3().setFromObject(aroundObject3D);
var centerpoint = BB.center();
var size = BB.size();
var backup = (size.y / 2) / Math.sin( (camera.fov/2)*(Math.PI/180) );
var camZpos = BB.max.z + backup + camera.near ;
//move cam
camera.position.set(centerpoint.x, centerpoint.y, camZpos);
camera.far = camera.near + 10*size.z;
camera.updateProjectionMatrix();
}
答案 2 :(得分:-1)
感谢Blindmann67,我能够更好地理解所有这些复杂性,并在threejs中提出解决方案。有一个特定的行,其中手动设置了刻度尺:
box.scale.x = box.scale.y = box.scale.z = .2;
因为我知道对象的宽度(动态)和画布的大小(500px),所以我能够通过以下方式获得正确的比例:
var initialZoomScale = 500 / canvasWidthProdPixels;
// canvasWidthProdPixels is my large object (ex. 25,000 px wide) dividing these gives me a small fraction such as .02 which scales the object perfectly)
box.scale.x = box.scale.y = box.scale.z = initialZoomScale;