Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Can we draw "lines" with Cube to show "Dimensions" at run time?

Here is how I have created the cube and getting dimensions from user and changing the cube at run time: http://jsfiddle.net/9Lvk61j3/

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

This is what I am trying to make as 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 is the Fiddle for the same! http://jsfiddle.net/9Lvk61j3/

Let me know if you need any other information.

Please suggest.

like image 766
UID Avatar asked Aug 21 '14 17:08

UID


1 Answers

There's a bit of a problem with drawing dimensions:

  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 away from the object,
    • some may overlay other dimensions (or even object elements),
    • some may be seen from inconvenient angle.
  2. The text should retain perfectly same size, no matter how you navigate camera,

Most of these points are addressed 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 see into helper classes now.

✔ Dimension line is only visible when camera angle is not too sharp (more than 45°),

class FacingCamera will let you know world plane, that is best facing to the camera. Useful to hide dimensions, which are facing camera with too sharp (acute) angle.

Separate fiddle to play 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);
            }
        }
    }
}

✔ Dimension text is HTML element, styled with CSS and positioned with three.js raycasting logic.

class LinearDimension creates an instance of linear dimension with arrows and text label, and controls it.

LinearDimension complete 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;
        }
    }
}
like image 173
Alex Khoroshylov Avatar answered Oct 11 '22 21:10

Alex Khoroshylov