Draw dimension lines along with a 3D cube using Three.js

Can we draw "lines" with Cube to display "Dimensions" at runtime?

This is how I created the cube and got the dimensions from the user and changed the cube at runtime: http://jsfiddle.net/9Lvk61j3/

But now I want to show Dimension, so the user knows what the length, width and height they will change.

Here's what I'm trying to do as the end result:

enter image description here

Here is my code: 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;
};

      

Here's a violin for the same! http://jsfiddle.net/9Lvk61j3/

Let me know if you need any other information.

Please suggest.

+4


source to share


1 answer


There is a small problem with the dimensions of the drawing:

  1. You may have many of them, and not all of them may be perfectly visible:
    • some may be hidden,
    • some may appear too small if the camera is far from the subject,
    • some may overlap other dimensions (or even elements of an object),
    • some may be seen from an awkward angle.
  2. The text must remain exactly the same size, no matter how you move around the camera,

Most of these points are covered in my solution: 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);
};

      


Let's take a look at the helper classes now.

Line The dimension line is visible only when the camera tilt angle is not too sharp (more than 45 °),



class FacingCamera

will let you know the world plane that looks best at the camera. It is useful to hide dimensions that are facing the camera at an overly acute (acute) angle.

A separate fiddle for playing with class FacingCamera

can be found here: https://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);
            }
        }
    }
}

      

Text Dimension text is an HTML element styled with CSS and positioned using three .js logic.

class LinearDimension

instantiates and manages a linear dimension with arrows and a text label.

Full LinearDimension implementation:

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;
        }
    }
}

      

0


source







All Articles