3

I'm trying to construct a collection of flat shapes in three.js. Each one is defined as a series of coplanar Vector3 points, but the shapes are not all coplanar. Imagine two flat rectangles as the roof of a house, but with much more complex shapes.

I can make flat Shape objects and then rotate and position them, but since my shapes are conceived in 3d coordinates, it would be much simpler to keep it all in 3-space, which the Shape object doesn't like.

Is there some much more direct way to simply specify an array of coplanar Vector3's, and let three.js do the rest of the work?

gunther47
  • 85
  • 1
  • 7
  • See https://stackoverflow.com/questions/37860895/three-js-shape-from-random-points/37863323#37863323. It is up to you if it is acceptable in your case. – WestLangley Feb 28 '18 at 04:11
  • This is very close to what I'm looking for. Unfortunately it only works on convex shapes. A blocky "L" shape is drawn as a wonky pentagon, if that makes sense. I'm really looking for a solution that will do something very similar to this, but with any shape, following the points in order instead of skinning the group. Thanks for your help! – gunther47 Feb 28 '18 at 22:52

1 Answers1

4

I thought about this problem and came up with the idea, when you have a set of co-planar points and you know the normal of the plane (let's name it normal), which your points belong to.

  1. We need to rotate our set of points to make it parallel to the xy-plane, thus the normal of that plane is [0, 0, 1] (let's name it normalZ). To do it, we find quaternions with .setFromUnitVectors() of THREE.Quaternion():

    var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ); var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);

  2. Apply quaternion to our set of points

  3. As it's parallel to xy-plane now, z-coordinates of points don't matter, so we can now create a THREE.Shape() object of them. And then create THREE.ShapeGeometry() (name it shapeGeom) from given shape, which will triangulate our shape.

  4. We need to put our points back to their original positions, so we'll apply quaternionBack to them.

  5. After all, we'll assign our set of points to the .vertices property of the shapeGeom.

That's it. If it'll work for you, let me know ;)

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 20, 40);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(10, 0, 10);
controls.update();

var grid = new THREE.GridHelper(50, 50, 0x808080, 0x202020); // xy-grid
grid.geometry.rotateX(Math.PI * 0.5);
scene.add(grid);

var points = [ // all of them are on the xz-plane
    new THREE.Vector3(5, 0, 5),
  new THREE.Vector3(25, 0, 5),
  new THREE.Vector3(25, 0, 15),
  new THREE.Vector3(15, 0, 15),
  new THREE.Vector3(15, 0, 25),
  new THREE.Vector3(5, 0, 25),
  new THREE.Vector3(5, 0, 5)
]

var geom = new THREE.BufferGeometry().setFromPoints(points);
var pointsObj = new THREE.Points(geom, new THREE.PointsMaterial({
  color: "red"
}));
scene.add(pointsObj);

var line = new THREE.LineLoop(geom, new THREE.LineBasicMaterial({
  color: "aqua"
}));
scene.add(line);

// normals 
var normal = new THREE.Vector3(0, 1, 0); // I already know the normal of xz-plane ;)
scene.add(new THREE.ArrowHelper(normal, new THREE.Vector3(10, 0, 10), 5, 0xffff00)); //yellow

var normalZ = new THREE.Vector3(0, 0, 1); // base normal of xy-plane
scene.add(new THREE.ArrowHelper(normalZ, scene.position, 5, 0x00ffff)); // aqua

// 1 quaternions
var quaternion = new THREE.Quaternion().setFromUnitVectors(normal, normalZ);
var quaternionBack = new THREE.Quaternion().setFromUnitVectors(normalZ, normal);

// 2 make it parallel to xy-plane
points.forEach(p => {
  p.applyQuaternion(quaternion)
});

// 3 create shape and shapeGeometry
var shape = new THREE.Shape(points);
var shapeGeom = new THREE.ShapeGeometry(shape);

// 4 put our points back to their origins
points.forEach(p => {
  p.applyQuaternion(quaternionBack)
});

// 5 assign points to .vertices
shapeGeom.vertices = points;

var shapeMesh = new THREE.Mesh(shapeGeom, new THREE.MeshBasicMaterial({
  color: 0x404040
}));
scene.add(shapeMesh);

render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
body {
  overflow: hidden;
  margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three@0.90.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.90.0/examples/js/controls/OrbitControls.js"></script>
prisoner849
  • 16,894
  • 4
  • 34
  • 68
  • I really appreciate your clear explanation and example. I'm still struggling a little bit, though. I definitely need to learn more about quaternions. I'm having two difficulties. My polygons are still showing up as convex, though I checked the order of the points. Not sure what I'm doing wrong there. And this method seems to rotate the planes by one axis, without checking the orientation of the original coordinates. I'm still thinking about it. But I feel like I'm getting closer, thanks for your help! – gunther47 Mar 02 '18 at 03:48
  • potential clue to what I'm doing wrong: the polygons appear convex from the top, but if you rotate the model and look at the underside, all you see are the overlapping places which should have been subtracted from the tops. Three.js is fascinating and I'm still getting my head round it. – gunther47 Mar 02 '18 at 04:04
  • @gunther47 Could you provide a set of the points? So I can test my algorithm with something real :) – prisoner849 Mar 02 '18 at 06:09
  • I built a fiddle with your code, and subbed in my own points. Can't understand what the difference is. With your points it works, with mine it doesn't. Even drew mine out on graph paper to make sure they were in the right order. https://jsfiddle.net/ftf5tk7m/2/ – gunther47 Mar 03 '18 at 22:22
  • Interesting. You have to close the polygon with cloning the first point at the end of the set of points (I've updated the answer). And even more, points have to be in the clockwise order. At least, the do it like that in this [example](https://github.com/mrdoob/three.js/blob/d55897b8e9b2632896d8ac146a05b3b4be3668f8/examples/webgl_geometry_shapes.html#L231L236). Or maybe I misunderstand something myself :) – prisoner849 Mar 03 '18 at 23:12
  • That works, but only for the simplest shapes. I made another fiddle https://jsfiddle.net/ftf5tk7m/21/ to show. If you add in an additional crenelation, it doesn't like it and skins the outside shape. Then try commenting _out_ lines 28-31 and comment _in_ line 21, you can see even weirder behavior when you add in a single point in the middle of an existing line. Should I find it surprising that three.js doesn't already have a FlatShape3 object? Seems like it would be useful for surface-by-surface construction. – gunther47 Mar 04 '18 at 02:16
  • I'll continue to work on it then. Thanks for testing :) – prisoner849 Mar 04 '18 at 07:21