Wie kann eine 3D-Box mit ungleichen Seiten das Ansichtsfenster ausfüllen, unabhängig von der Ausrichtung in der Perspektive?
Wie im beiliegenden three.js) Live-Snippet (auch bei jsfiddle.net / gpolyn / une6tst5 / 21) *, Ich habe eine Box mit ungleichen Seiten, die ein Betrachter durch Ziehen neu positionieren kann. Die äußersten linken, rechten, oberen oder unteren Kastenecken im Ansichtsfenster werden dynamisch durch grüne quadratische Punkte angezeigt.
Meine Modellierungsherausforderung lautet wie folgt:Stellen Sie für ein bestimmtes Ansichtsfenster meine Box so vor, dass sich die Punkte mit dem größten Fensterabstand zwischen ihnen in allen Positionen an den jeweiligen Rändern des Ansichtsfensters befinden..
So kann für eine Objektausrichtung das Objekt mit einer linken und einer rechten gepunkteten Ecke am linken und rechten Rand des Ansichtsfensters dargestellt werden, während eine andere Ausrichtung zu einer Darstellung der oberen und unteren grünen gepunkteten Ecken am oberen und unteren Rand des Ansichtsfensters führen kann.
ein aktueller Ansatz verwendet eine begrenzende Sphäre, aber das erreicht mein Ziel für @ nichjede oder sogar viele Objektorientierungen.
Ich vermute, dass ein besserer Ansatz irgendwo zwischen diesen liegen könnte:
Ändern Sie abhängig von den Fensterkoordinaten der meisten extremen Objektpunkte die Ansicht oder die Projektionsmatrix oder beides, um das Objekt darzustellen Tauschen Sie den Bounding-Sphere-Ansatz gegen einen Bounding-Box-Ansatz ausLesen Sie die Fensterkoordinaten eines 'virtuellen' Rahmens um die grün gepunkteten Ecken und projizieren Sie das gerahmte Bild auf das Fenster (ähnlich wie bei 1).* Mein Code hängt stark von einer hervorragenden Präsentation von Eric Haines bei @ a www.realtimerendering.com / udacity / transforms.html, während die grüne Punkttechnik von einer der vielen höchst nützlichen three.js-Antworten stammt, die @ in diesem Forum gepostet h WestLangley
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>