如包含的 ( three.js ) 实时片段(也在jsfiddle.net/gpolyn/une6tst5/21 中)所示*,我有一个边不等边的框,查看者可以通过拖动重新定位。视口中最左、最右、最上或最下的方框角由绿色方点动态指示。
我的建模挑战如下:对于给定的视口,呈现我的盒子,以便通过所有位置,它们之间的窗口距离最长的点都在它们各自的视口边缘。
因此,对于一个对象方向,对象可能会在视口的左右边缘以左右虚线角呈现,而另一种方向可能会导致在视口的顶部和底部呈现顶部和底部的绿色虚线角。
我目前的方法使用边界球,但这并不能实现我对每个甚至许多面向对象的目标。
我怀疑更好的方法可能存在于这些之中:
- 根据最极端对象点的窗口坐标,修改视图或投影矩阵或两者,以表示对象
- 将边界球方法换成边界框方法
- 获取围绕绿色虚线角的“虚拟”框架的窗口坐标,并将框架图像投影到窗口上(类似于 1.)
*我的代码很大程度上依赖于 Eric Haines 在www.realtimerendering.com/udacity/transforms.html上的出色演示,而绿点技术来自 WestLangley 在此论坛上发布的许多非常有用的three.js 答案之一
var renderer, scene, camera, controls;
var object;
var vertices3;
var cloud;
var boxToBufferAlphaMapping = {
0: 0,
2: 1,
1: 2,
3: 4,
6: 7,
7: 10,
5: 8,
4: 6
}
var lastAlphas = [];
var canvasWidth, canvasHeight;
var windowMatrix;
var boundingSphere;
init();
render();
afterInit();
animate();
function init() {
canvasWidth = window.innerWidth;
canvasHeight = window.innerHeight;
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(canvasWidth, canvasHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// object
var geometry = new THREE.BoxGeometry(4, 4, 6);
// too lazy to add edges without EdgesHelper...
var material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0
});
var cube = new THREE.Mesh(geometry, material);
object = cube;
// bounding sphere used for orbiting control in render
object.geometry.computeBoundingSphere();
boundingSphere = object.geometry.boundingSphere;
cube.position.set(2, 2, 3)
// awkward, but couldn't transfer cube position to sphere...
boundingSphere.translate(new THREE.Vector3(2, 2, 3));
// save vertices for subsequent use
vertices = cube.geometry.vertices;
var edges = new THREE.EdgesHelper(cube)
scene.add(edges);
scene.add(cube);
addGreenDotsToScene(geometry);
// camera
camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(20, 20, 20);
// controls
controls = new THREE.OrbitControls(camera);
controls.maxPolarAngle = 0.5 * Math.PI;
controls.minAzimuthAngle = 0;
controls.maxAzimuthAngle = 0.5 * Math.PI;
controls.enableZoom = false;
// ambient
scene.add(new THREE.AmbientLight(0x222222));
// axes
scene.add(new THREE.AxisHelper(20));
}
// determine which object points are in the most extreme top-,
// left-, right- and bottom-most positions in the window space
// and illuminate them
function addExtrema() {
// object view-space points, using view (camera) matrix
var viewSpacePts = vertices3.map(function(vt) {
return vt.clone().applyMatrix4(camera.matrixWorldInverse);
})
// object clip coords, using projection matrix
var clipCoords = viewSpacePts.map(function(vt) {
return vt.applyMatrix4(camera.projectionMatrix);
})
// w-divide clip coords for NDC
var ndc = clipCoords.map(function(vt) {
return vt.divideScalar(vt.w);
})
// object window coordinates, using window matrix
var windowCoords = ndc.map(function(vt) {
return vt.applyMatrix4(windowMatrix);
})
// arbitrary selection to start
var topIdx = 0,
bottomIdx = 0,
leftIdx = 0,
rightIdx = 0;
var top = windowCoords[0].y;
var bottom = windowCoords[0].y
var right = windowCoords[0].x;
var left = windowCoords[0].x;
for (var i = 1; i < windowCoords.length; i++) {
vtx = windowCoords[i];
if (vtx.x < left) {
left = vtx.x;
leftIdx = i;
} else if (vtx.x > right) {
right = vtx.x;
rightIdx = i;
}
if (vtx.y < bottom) {
bottom = vtx.y;
bottomIdx = i;
} else if (vtx.y > top) {
top = vtx.y;
topIdx = i;
}
}
var alphas = cloud.geometry.attributes.alpha;
// make last points invisible
lastAlphas.forEach(function(alphaIndex) {
alphas.array[alphaIndex] = 0.0;
});
// now, make new points visible...
// (boxToBufferAlphaMapping is a BufferGeometry-Object3D geometry
// map between the object and green dots)
alphas.array[boxToBufferAlphaMapping[rightIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[bottomIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[topIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[leftIdx]] = 1.0;
// store visible points for next cycle
lastAlphas = [boxToBufferAlphaMapping[rightIdx]];
lastAlphas.push(boxToBufferAlphaMapping[bottomIdx])
lastAlphas.push(boxToBufferAlphaMapping[topIdx])
lastAlphas.push(boxToBufferAlphaMapping[leftIdx])
alphas.needsUpdate = true;
}
function addGreenDotsToScene(geometry) {
var bg = new THREE.BufferGeometry();
bg.fromGeometry(geometry);
bg.translate(2, 2, 3); // yucky, and quick
var numVertices = bg.attributes.position.count;
var alphas = new Float32Array(numVertices * 1); // 1 values per vertex
for (var i = 0; i < numVertices; i++) {
alphas[i] = 0;
}
bg.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
var uniforms = {
color: {
type: "c",
value: new THREE.Color(0x00ff00)
},
};
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true
});
cloud = new THREE.Points(bg, shaderMaterial);
scene.add(cloud);
}
function afterInit() {
windowMatrix = new THREE.Matrix4();
windowMatrix.set(canvasWidth / 2, 0, 0, canvasWidth / 2, 0, canvasHeight / 2, 0, canvasHeight / 2, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
var vertices2 = object.geometry.vertices.map(function(vtx) {
return (new THREE.Vector4(vtx.x, vtx.y, vtx.z));
});
// create 'world-space' geometry points, using
// model ('world') matrix
vertices3 = vertices2.map(function(vt) {
return vt.applyMatrix4(object.matrixWorld);
})
}
function render() {
var dist = boundingSphere.distanceToPoint(camera.position);
// from stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
var height = boundingSphere.radius * 2;
var fov = 2 * Math.atan(height / (2 * dist)) * (180 / Math.PI);
// not sure why, but factor is needed to maximize fit of object
var mysteryFactor = 0.875;
camera.fov = fov * mysteryFactor;
camera.updateProjectionMatrix();
camera.lookAt(boundingSphere.center);
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
addExtrema()
}
body {
background-color: #000;
margin: 0px;
overflow: hidden;
}
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_PointSize = 8.0; gl_Position = projectionMatrix * mvPosition; }
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color; varying float vAlpha; void main() { gl_FragColor = vec4( color, vAlpha ); }
</script>