使用Three.js在3D立方体上绘制尺寸线。

9
我们能用 Cube 在运行时展示“维度”并画出“线条”吗?
这是我创建 Cube 并在运行时从用户获取维度并更改 Cube 的方式:http://jsfiddle.net/9Lvk61j3/ 但现在我想展示维度,以便用户知道长度、宽度和高度是多少,因为他们将会进行更改。
这就是我想要的最终结果: enter image description here 以下是我的代码: HTML:
<script src="http://www.html5canvastutorials.com/libraries/three.min.js"></script>
<div id="container"></div>
<div class="inputRow clear" id="dimensionsNotRound" data-role="tooltip">
    <label class="grid-8">Dimensions (pixels):</label>
    <br/>
    <br/>
    <div> <span>Length</span>

        <input class="numeric-textbox" id="inp-length" type="text" value="100">
        <br/>
        <br/>
    </div>
    <div> <span>Width</span>

        <input class="numeric-textbox" id="inp-width" type="text" value="50">
        <br/>
        <br/>
    </div>
    <div> <span>Height</span>

        <input class="numeric-textbox" id="inp-height" type="text" value="40">
        <br/>
        <br/>
    </div>
    <button id="btn">Click me to change the Dimensions</button>

JS

    var shape = null;


    //Script for 3D Box


    // revolutions per second
    var angularSpeed = 0.2;
    var lastTime = 0;
    var cube = 0;

    // this function is executed on each animation frame
    function animate() {
        // update
        var time = (new Date()).getTime();
        var timeDiff = time - lastTime;
        var angleChange = angularSpeed * timeDiff * 2 * Math.PI / 1000;
        //cube.rotation.y += angleChange; //Starts Rotating Object
        lastTime = time;

        // render
        renderer.render(scene, camera);

        // request new frame
        requestAnimationFrame(function () {
            animate();
        });
    }

    // renderer
    var container = document.getElementById("container");
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(container.offsetWidth, container.offsetHeight - 4);
    container.appendChild(renderer.domElement);


    // camera
    var camera = new THREE.PerspectiveCamera(60, container.offsetWidth / container.offsetHeight, 1, 1000);
    camera.position.z = 800;

    // scene
    var scene = new THREE.Scene();
    scene.remove();

    // cube
    cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshLambertMaterial({
        color: '#cccccc'
    }));
    cube.overdraw = true;

    cube.rotation.x = Math.PI * 0.1;
    cube.rotation.y = Math.PI * 0.3;
    scene.add(cube);

    // add subtle ambient lighting
    var ambientLight = new THREE.AmbientLight(0x319ec5);
    scene.add(ambientLight);

    // directional lighting
    var directionalLight = new THREE.DirectionalLight(0x666666);
    directionalLight.position.set(1, 1, 1).normalize();
    scene.add(directionalLight);
    shape = cube;
    // start animation
    animate();

var $ = function(id) { return document.getElementById(id); };

$('btn').onclick = function() {
    console.log("Button Clicked");
    var width = parseInt(document.getElementById('inp-width').value * 3.779528),
        height = parseInt(document.getElementById('inp-height').value * 3.779528),
        length = parseInt(document.getElementById('inp-length').value * 3.779528);
console.log("length " + length + " height " + height + " width " + width);

    shape.scale.x = length;
    shape.scale.y = height;
    shape.scale.z = width;
};

这里有相同内容的Fiddle!http://jsfiddle.net/9Lvk61j3/

如果您需要其他信息,请告诉我。

请提供建议。


2
你可能需要使用一些ArrowHelper [http://jsfiddle.net/9Lvk61j3/1/],并在“btn”点击时修复比例和位置。 - Abraham Uribe
1
谢谢您的关注!您能否帮忙创建一个工作示例,至少显示高度和宽度的箭头?同时有没有办法显示两侧箭头?如果无法显示,我可以用线条替换相同的代码吗?再次感谢……请更新您的答案,以便我能接受。 - UID
1个回答

7

在绘制尺寸方面存在一些问题:

  1. 您可能会有很多尺寸,而且并不是所有的尺寸都能完美地显示出来:
    • 有些可能被隐藏,
    • 如果相机远离物体,则某些尺寸可能显得太小,
    • 某些尺寸可能重叠其他尺寸(甚至物体元素),
    • 某些尺寸可能从不方便的角度看到。
  2. 文本应该保持完全相同的大小,无论您如何导航相机。

我的解决方案解决了大部分这些问题:https://jsfiddle.net/mmalex/j35p1fw8/

threejs show object dimensions with text and arrows

var geometry = new THREE.BoxGeometry(8.15, 0.5, 12.25);
var material = new THREE.MeshPhongMaterial({
  color: 0x09f9f9,
  transparent: true,
  opacity: 0.75
});
var cube = new THREE.Mesh(geometry, material);
cube.geometry.computeBoundingBox ();
root.add(cube);

var bbox = cube.geometry.boundingBox;

var dim = new LinearDimension(document.body, renderer, camera);

// define start and end point of dimension
var from = new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z);
var to = new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z);

// in which direction to "extrude" dimension away from object
var direction = new THREE.Vector3(0, 0, 1);

// request LinearDimension to create threejs node
var newDimension = dim.create(from, to, direction);

// make it cube child
cube.add(newDimension);

var animate = function() {
  requestAnimationFrame(animate);

  // we need to reposition dimension label on each camera change
  dim.update(camera);

  renderer.render(scene, camera);
};

现在让我们了解一下辅助类。

✔ 当摄像机角度不太陡峭(小于45°)时,尺寸线才可见,class FacingCamera 将告诉你最适合面向摄像机的世界平面。对于需要隐藏那些与摄像机夹角太大(锐角)的尺寸来说非常有用。

可以在此处找到单独的示例来使用 class FacingCamerahttps://jsfiddle.net/mmalex/56gzn8pL/

class FacingCamera {
    constructor() {
        // camera looking direction will be saved here
        this.dirVector = new THREE.Vector3();

        // all world directions
        this.dirs = [
            new THREE.Vector3(+1, 0, 0),
            new THREE.Vector3(-1, 0, 0),
            new THREE.Vector3(0, +1, 0),
            new THREE.Vector3(0, -1, 0),
            new THREE.Vector3(0, 0, +1),
            new THREE.Vector3(0, 0, -1)
        ];

        // index of best facing direction will be saved here
        this.facingDirs = [];
        this.bestFacingDir = undefined;

        // TODO: add other facing directions too

        // event listeners are collected here
        this.cb = {
            facingDirChange: []
        };
    }

    check(camera) {
        camera.getWorldDirection(this.dirVector);
        this.dirVector.negate();

        var maxk = 0;
        var maxdot = -1e19;

        var oldFacingDirs = this.facingDirs;
        var facingDirsChanged = false;
        this.facingDirs = [];

        for (var k = 0; k < this.dirs.length; k++) {
            var dot = this.dirs[k].dot(this.dirVector);
            var angle = Math.acos(dot);
            if (angle > -Math.PI / 2 && angle < Math.PI / 2) {
                this.facingDirs.push(k);
                if (oldFacingDirs.indexOf(k) === -1) {
                    facingDirsChanged = true;
                }
                if (Math.abs(dot) > maxdot) {
                    maxdot = dot;
                    maxk = k;
                }
            }
        }

        // and if facing direction changed, notify subscribers
        if (maxk !== this.bestFacingDir || facingDirsChanged) {
            var prevDir = this.bestFacingDir;
            this.bestFacingDir = maxk;

            for (var i = 0; i < this.cb.facingDirChange.length; i++) {
                this.cb.facingDirChange[i]({
                    before: {
                        facing: oldFacingDirs,
                        best: prevDir
                    },
                    current: {
                        facing: this.facingDirs,
                        best: this.bestFacingDir
                    }
                }, this);
            }
        }
    }
}

✔ 尺寸文本是HTML元素,使用CSS样式并通过three.js射线投射逻辑定位。

class LinearDimension 创建具有箭头和文本标签的线性尺寸实例,并对其进行控制。

LinearDimension 的完整实现:

class LinearDimension {

    constructor(domRoot, renderer, camera) {
        this.domRoot = domRoot;
        this.renderer = renderer;
        this.camera = camera;

        this.cb = {
            onChange: []
        };
        this.config = {
            headLength: 0.5,
            headWidth: 0.35,
            units: "mm",
            unitsConverter: function(v) {
                return v;
            }
        };
    }

    create(p0, p1, extrude) {

        this.from = p0;
        this.to = p1;
        this.extrude = extrude;

        this.node = new THREE.Object3D();
        this.hidden = undefined;

        let el = document.createElement("div");
        el.id = this.node.id;
        el.classList.add("dim");
        el.style.left = "100px";
        el.style.top = "100px";
        el.innerHTML = "";
        this.domRoot.appendChild(el);
        this.domElement = el;

        this.update(this.camera);

        return this.node;
    }

    update(camera) {
        this.camera = camera;

        // re-create arrow
        this.node.children.length = 0;

        let p0 = this.from;
        let p1 = this.to;
        let extrude = this.extrude;

        var pmin, pmax;
        if (extrude.x >= 0 && extrude.y >= 0 && extrude.z >= 0) {
            pmax = new THREE.Vector3(
                extrude.x + Math.max(p0.x, p1.x),
                extrude.y + Math.max(p0.y, p1.y),
                extrude.z + Math.max(p0.z, p1.z));

            pmin = new THREE.Vector3(
                extrude.x < 1e-16 ? extrude.x + Math.min(p0.x, p1.x) : pmax.x,
                extrude.y < 1e-16 ? extrude.y + Math.min(p0.y, p1.y) : pmax.y,
                extrude.z < 1e-16 ? extrude.z + Math.min(p0.z, p1.z) : pmax.z);
        } else if (extrude.x <= 0 && extrude.y <= 0 && extrude.z <= 0) {
            pmax = new THREE.Vector3(
                extrude.x + Math.min(p0.x, p1.x),
                extrude.y + Math.min(p0.y, p1.y),
                extrude.z + Math.min(p0.z, p1.z));

            pmin = new THREE.Vector3(
                extrude.x > -1e-16 ? extrude.x + Math.max(p0.x, p1.x) : pmax.x,
                extrude.y > -1e-16 ? extrude.y + Math.max(p0.y, p1.y) : pmax.y,
                extrude.z > -1e-16 ? extrude.z + Math.max(p0.z, p1.z) : pmax.z);
        }

        var origin = pmax.clone().add(pmin).multiplyScalar(0.5);
        var dir = pmax.clone().sub(pmin);
        dir.normalize();

        var length = pmax.distanceTo(pmin) / 2;
        var hex = 0x0;
        var arrowHelper0 = new THREE.ArrowHelper(dir, origin, length, hex, this.config.headLength, this.config.headWidth);
        this.node.add(arrowHelper0);

        dir.negate();
        var arrowHelper1 = new THREE.ArrowHelper(dir, origin, length, hex, this.config.headLength, this.config.headWidth);
        this.node.add(arrowHelper1);

        // reposition label
        if (this.domElement !== undefined) {
            let textPos = origin.project(this.camera);

            let clientX = this.renderer.domElement.offsetWidth * (textPos.x + 1) / 2 - this.config.headLength + this.renderer.domElement.offsetLeft;

            let clientY = -this.renderer.domElement.offsetHeight * (textPos.y - 1) / 2 - this.config.headLength + this.renderer.domElement.offsetTop;

            let dimWidth = this.domElement.offsetWidth;
            let dimHeight = this.domElement.offsetHeight;

            this.domElement.style.left = `${clientX - dimWidth/2}px`;
            this.domElement.style.top = `${clientY - dimHeight/2}px`;

            this.domElement.innerHTML = `${this.config.unitsConverter(pmin.distanceTo(pmax)).toFixed(2)}${this.config.units}`;
        }
    }

    detach() {
        if (this.node && this.node.parent) {
            this.node.parent.remove(this.node);
        }
        if (this.domElement !== undefined) {
            this.domRoot.removeChild(this.domElement);
            this.domElement = undefined;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接