In visual development, no matter 2d canvas or 3D development, line drawing is very common, such as drawing migration map and movement track map between cities. All objects, whether in three or two dimensions, are made up of points, two points form a line and three points form a surface. So what’s the story behind drawing a simple line in ThreeJS? In this article, we’ll unravel one by one.

The birth of a thread

In ThreeJS, objects are made up of Geometry and Material, and how objects are presented (point, line, plane) depends on how they are rendered (ThreeJS offers different object constructors).

If you look at ThreeJS ‘API, there are some lines related to it:

In simple terms, ThreeJS provides LineBasicMaterial and LineDashedMaterial, which mainly control the color and width of the line. Geometry mainly controls the position of the breakpoint of the line segment, and mainly uses the basic geometry class BufferGeometry to create line geometry. Some line generating functions are also provided to help generate line geometry.

A straight line

Line LineLoop LineSegments three classes of Line related objects are provided in the API

Line

Use Line to create the simplest possible Line:

// Create a material
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// Create empty geometry
const geometry = new THREE.BufferGeometry()
const points = [];
points.push(new THREE.Vector3(20.20.0));
points.push(new THREE.Vector3(20, -20.0));
points.push(new THREE.Vector3(-20, -20.0));
points.push(new THREE.Vector3(-20.20.0));
// Bind vertices to empty geometry
geometry.setFromPoints(points);

const line = new THREE.Line(geometry, material);
scene.add(line);
Copy the code

LineLoop

LineLoop is used to draw a series of points into a continuous Line. It is almost the same as Line, but the only difference is that the first point will be connected to the last point after all the points are connected. This kind of Line is used to draw a certain area in actual projects, such as selecting a certain area on a map with a Line. Create an object using LineLoop:

// Create a material
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// Create empty geometry
const geometry = new THREE.BufferGeometry()
const points = [];
points.push(new THREE.Vector3(20.20.0));
points.push(new THREE.Vector3(20, -20.0));
points.push(new THREE.Vector3(-20, -20.0));
points.push(new THREE.Vector3(-20.20.0));
// Bind vertices to empty geometry
geometry.setFromPoints(points);

const line = new THREE.LineLoop(geometry, material);
scene.add(line);
Copy the code

The same four points, created using LineLoop, are a closed region.

LineSegments

LineSegments is used to connect two points into a line, it will automatically allocate a series of points we pass into two groups, and then connect the two allocated points. This kind of congenital actual project is mainly used to draw lines with the same starting point and different ending point, such as commonly used genetic maps. Create an object using LineSegments:

// Create a material
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// Create empty geometry
const geometry = new THREE.BufferGeometry()
const points = [];
points.push(new THREE.Vector3(20.20.0));
points.push(new THREE.Vector3(20, -20.0));
points.push(new THREE.Vector3(-20, -20.0));
points.push(new THREE.Vector3(-20.20.0));
// Bind vertices to empty geometry
geometry.setFromPoints(points);

const line = new THREE.LineSegments(geometry, material);
scene.add(line);
Copy the code

The difference between

The difference between the above three line objects is that the WebGL method of the underlying rendering is different. Suppose there are five points p1/ P2 / P3 / P4 / P5,

  • LineUsing thegl.LINE_STRIP, draw a line to the next vertex, the final line is P1 -> P2 -> p3 -> P4 -> p5
  • LineLoopUsing thegl.LINE_LOOP, draw a line to the next vertex, and return the last vertex to the first vertex, the final line is P1 -> P2 -> p3 -> P4 -> p5 -> P1
  • LineSegmentsUsing thegl.LINES, draw a line between a pair of vertices, the final line is P1 -> P2 p3 -> P4

If you just draw a line segment between two points, then there is no difference between the above three implementations, the implementation effect is the same.

Dotted line

In addition to LineBasicMaterial, ThreeJS also provides LineDashedMaterial to draw dashed lines:

// Dashed material
const material = new THREE.LineDashedMaterial({
  color: 0xff0000.scale: 1.dashSize: 3.gapSize: 1});const points = [];
points.push(new THREE.Vector3(10.10.0));
  points.push(new THREE.Vector3(10, -10.0));
  points.push(new THREE.Vector3(-10, -10.0));
  points.push(new THREE.Vector3(-10.10.0));
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
// Compute an array of values for the distance required by LineDashedMaterial.
line.computeLineDistances();
scene.add(line);
Copy the code

Note that you need to calculate the distance between the lines to draw the dotted line, otherwise the dotted line effect will not appear. For each vertex of geometry, line.com puteLineDistances this method to calculate the accumulation, the starting point of the current point to the line length.

Cool line

Add some width

LineBasicMaterial provides a lineWidth, linecap, and linecAP for adjacent line segments, but this does not work. ThreeJS documentation shows this:

Due to the limitations of the underlying OpenGL rendering, the maximum and minimum values of the line width can only be 1, and the line width cannot be set, so the setting of the connecting shape between line segments is meaningless, so these three Settings cannot take effect.

ThreeJS has officially provided a demo that can set line width using the material LineMaterial, the geometry and the object Line2 from the JSM extension.

import { Line2 } from './jsm/lines/Line2.js';
import { LineMaterial } from './jsm/lines/LineMaterial.js';
import { LineGeometry } from './jsm/lines/LineGeometry.js';

const geometry = new LineGeometry();
geometry.setPositions( positions );

const matLine = new LineMaterial({
  color: 0xffffff.linewidth: 5.// in world units with size attenuation, pixels otherwise
  //resolution: // to be set by renderer, eventually
  dashed: false.alphaToCoverage: true});const line = new Line2(geometry, matLine);
line.computeLineDistances();
line.scale.set(1.1.1);
scene.add( line );

function animate() {
  renderer.render(scene, camera);
	// renderer will set this eventually
  matLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport
  requestAnimationFrame(animate);
}
Copy the code

Note that in the render loop, the resolution of the material needs to be reset for each frame, otherwise the width effect will not work. No documentation is provided for Line2. The specific parameters need to be explored by observing the source code.

Add some color

In the basic demo, we used the color of the material to set the color of the line uniformly. What if we wanted to achieve a gradient effect?

In material Settings, the vertexColors parameter controls the source of the material color. If true, the color calculation logic is derived from the vertex color, which is smoothed by interpolation into a continuous color change.

// Create a material
const material = new THREE.LineMaterial({
  linewidth: 2.vertexColors: true.resolution: new THREE.Vector2(800.600)});// Create empty geometry
const geometry = new THREE.LineGeometry();
geometry.setPositions([
  10.10.0.10, -10.0, -10, -10.0, -10.10.0
]);
// Set the vertex color
geometry.setColors([
  1.0.0.0.1.0.0.0.1.1.1.0
]);

const line = new THREE.Line2(geometry, material);
line.computeLineDistances();
scene.add(line);
Copy the code

The code creates four points and sets the vertex colors to red (1,0,0), green (0,1,0), blue (0,0,1), and yellow (1,1,0). The resulting rendering looks like this:

This example only sets the color of the four vertices. If the color interpolation function spacing is smaller, we can create a more detailed color.

Add some shape

Two points can be connected to specify a line, if the spacing between the points is very small, and the points are very dense, can be connected to generate a variety of curves.

ThreeJS provides a variety of curve generating functions, mainly divided into two-dimensional curve and three-dimensional curve:

  • ArcCurveEllipseCurveDraw circles and ellipses,EllipseCurveArcCurveThe base class;
  • LineCurveLineCurve3Draw two – and three-dimensional curves (the mathematical definition of a curve includes a straight line), both of which consist of starting points and ending points;
  • QuadraticBezierCurve,QuadraticBezierCurve3,CubicBezierCurveandCubicBezierCurve3They are two, three, two and threeBessel curve;
  • SplineCurveCatmullRomCurve3Are two – dimensional and three – dimensional spline curves respectively, usingCatmull-RomAlgorithm to create a smooth spline curve from a series of points.

Bessel curve differs from CatmullRom curve in that CatmullRom curve can smoothly pass through all points and is generally used to draw a trajectory, while Bessel curve constructs tangents through intermediate points.

  • Bessel curve

  • CatmullRom curve

These constructors generate curves by parameters. The base class Curve provides getPoints method class to obtain points on the Curve. The parameter is the number of segments of the Curve. Finally, assign the series of points to the geometry, take Bezier curve as an example:

// Create geometry
const geometry = new THREE.BufferGeometry();
// Create a curve
const curve = new THREE.CubicBezierCurve3(
  new THREE.Vector3(-10, -20, -10),
  new THREE.Vector3(-10.40, -10),
  new THREE.Vector3(10.40.10),
  new THREE.Vector3(10, -20.10));The getPoints method retrieves points from the curve
const points = curve.getPoints(100);
// Assign the series of points to the geometry
geometry.setFromPoints(points);
// Create a material
const material = new THREE.LineBasicMaterial({color: 0xff0000});
const line = new THREE.Line(geometry, material);
scene.add(line);
Copy the code

We can also implement a custom Curve by inheriting the Curve base class and overwriting the getPoint method in the base class, which returns a vector at a given position t in the Curve.

For example, to implement a sine curve:

class CustomSinCurve extends THREE.Curve {
	constructor( scale = 1 ) {
		super(a);this.scale = scale;
	}

	getPoint( t, optionalTarget = new THREE.Vector3() ) {
		const tx = t * 3 - 1.5;
		const ty = Math.sin( 2 * Math.PI * t );
		const tz = 0;

		return optionalTarget.set( tx, ty, tz ).multiplyScalar( this.scale ); }}Copy the code

Add some stretching

A line, no matter how it changes, is just a two-dimensional plane, although there are some three-dimensional curves above, but the normal plane is different. If we want to simulate some effect similar to pipe, pipe is the concept of diameter, then two-dimensional line certainly cannot meet the requirements. So we need to use other geometry to achieve the pipe effect.

ThreeJS packages a number of geometers for us to use, including the TubeGeometry Pipe, which stretches out a pipe based on a 3D curve. Its constructor is:

class TubeGeometry(path : Curve.tubularSegments : Integer.radius : Float.radialSegments : Integer.closed : Boolean)
Copy the code

Path is a curve that describes the shape of the pipe. We use the sinusoidal curve CustomSinCurve we created earlier to generate a curve and use TubeGeometry to stretch it.

const tubeGeometry = new THREE.TubeGeometry(new CustomSinCurve(10), 20.2.8.false);
const tubeMaterial = new THREE.MeshStandardMaterial({ color: 0x156289.emissive: 0x072534.side: THREE.DoubleSide });
const tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
scene.add(tube)
Copy the code

Add some animation

At this point, our line has a width, color, and shape, so it’s time to move! The essence of moving is to change a certain property of the object in each rendering frame to form a certain continuous effect, so we have two ideas to make the line move, one is to make the geometry of the line move, the other is to make the material of the line move.

The flow line

Texture flow is the most frequently used texture animation. The texture flows by setting the repeat property of the texture and changing the offset of the texture object.

To achieve texturing flow in a line, 2d lines cannot be achieved and must be stretched in 3d pipes to make sense. Use the same pipe body we implemented earlier and apply a texture configuration to the material:

// Create a texture
const imgUrl = 'xxx'; // Image address
const texture = new THREE.TextureLoader().load(imgUrl);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// Control texture repeat parameters
texture.repeat.x = 10;
texture.repeat.y = 1;
// Apply the texture to the material
const tubeMaterial = new THREE.MeshStandardMaterial({
   color: 0x156289.emissive: 0x156289.map: texture,
   side: THREE.DoubleSide,
});
const tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
scene.add(tube)

function renderLoop() {
  const delta = clock.getDelta();
  renderer.render(scene, camera);
  // Update texture offset in renderloop
  if (texture) {
    texture.offset.x -= 0.01;
  }
  requestAnimationFrame(renderLoop);
}
Copy the code

demo

The growth of the line

The realization idea of the growing line is very simple. First calculate and define a series of points, that is, the final shape of the line, and then create a line with only the first two points, and then insert other points into the created line in order, and then update the line, and finally get the effect of the line growing.

The renewal of the BufferGeometry

Before we do that, let’s look at geometry in ThreeJS again. Geometry in ThreeJS can be divided into Points, lines and Mesh. Points of model creation object is formed by Points, each point has its own position, the Line object model to create is a continuous Line, the Line can be understood as connect all Points in sequence, Mesh grid model creation object is composed of small triangle, these small triangle is determined by three Points. No matter what kind of model, they all have one thing in common, that is, they are inseparable from points, and each point has a definite X, y, z. BoxGeometry and SphereGeometry help us encapsulate the operation of these points. We only need to tell them the information of length, width, height or radius. It’s going to help me create a default geometry. BufferGeometry is a method to completely manipulate the point information by ourselves, through which we can set the position, color and normal vector of each point.

In contrast to Geometry, BufferGeometry stores information (such as vertex positions, plane indexes, normals, colors, UVs, and any custom attributes) in buffers — namely, Typed Arrays. This makes them usually faster than standard Geometry, but has the disadvantage of being harder to use.

The most important point when updating the BufferGeometry is that the size of the buffer cannot be adjusted, which is equivalent to creating a new geometry, but the contents of the buffer can be updated. So if you expect some property of the BufferGeometry to increase, such as the number of vertices, you must pre-allocate a buffer large enough to accommodate any number of new vertices that may be created. Of course, this also means that The BufferGeometry will have a maximum size, which means that there is no way to create a BufferGeometry that can efficiently scale indefinitely.

So, when drawing growing lines, the real problem is to extend the vertices of the lines at render time. For example, we first allocate a buffer of 500 vertices to the vertex attributes of BufferGeometry, but initially draw only two, and then control the drawn buffer range using the drawRange method of BufferGeometry.

const MAX_POINTS = 500;
// Create geometry
const geometry = new THREE.BufferGeometry();

// Set the geometry's properties
const positions = new Float32Array( MAX_POINTS * 3 ); // A vertex vector needs three position descriptions
geometry.setAttribute( 'position'.new THREE.BufferAttribute( positions, 3));// Control the drawing range
const drawCount = 2; // Draw only the first two points
geometry.setDrawRange( 0, drawCount );

// Create a material
const material = new THREE.LineBasicMaterial( { color: 0xff0000});/ / create a line
const line = new THREE.Line( geometry, material );
scene.add(line);
Copy the code

Then randomly add vertices to the line:

const positions = line.geometry.attributes.position.array;

let x, y, z, index;
x = y = z = index = 0;

for ( let i = 0; i < MAX_POINTS; i ++ ) {
    positions[ index ++ ] = x;
    positions[ index ++ ] = y;
    positions[ index ++ ] = z;

    x += ( Math.random() - 0.5 ) * 30;
    y += ( Math.random() - 0.5 ) * 30;
    z += ( Math.random() - 0.5 ) * 30;

}
Copy the code

To change the number of points rendered after the first render, do the following:

line.geometry.setDrawRange(0, newValue);
Copy the code

If you want to change the position value after the first rendering, you need to set the needsUpdate flag:

line.geometry.attributes.position.needsUpdate = true; // Need to be added after the first rendering
Copy the code

demo

Line drawing

In the editor of 3d building scene, it is often necessary to draw the connection between objects, such as drawing pipelines in industrial scene, drawing shelves in modeling scene, etc. This process can be abstracted as clicking two points on the screen to generate a straight line. This doesn’t sound difficult in a 2D scene, but how do you do it in 3D?

First to solve is the line of the vertex update, the mouse click on a vertex of a certain line, and click ok again next vertex positions, the second is to solve the 3 d scenario click and interaction problems, how to determine the three-dimensional point in 2 d screen position, how to ensure that users to click on the location of the point is that its understanding.

The renewal of the LineGeometry

When drawing ordinary lines, BufferGeometry is used, and we also explained how to update this in the previous section. However, in the section of drawing lines with width, we use the material LineMaterial, the geometry, and the object Line2 from the EXTENSION package JSM. How should LineGeometry be renewed?

LineGeometry provides methods for setPosition that operate on its BufferAttribute, so we don’t need to worry about how to update

Looking through the source code, it can be seen that the bottom rendering of LineGeometry is not directly calculated by the position attribute, but by the attribute instanceStart instanceEnd to set. LineGeometry provides setPositions to update the vertex of the line.

class LineSegmentsGeometry {
  // ...
  setPositions( array ) {
		let lineSegments;
		if ( array instanceof Float32Array ) {
			lineSegments = array;
		} else if ( Array.isArray( array ) ) {
			lineSegments = new Float32Array( array );
		}
		const instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6.1 ); // xyz, xyz
		this.setAttribute( 'instanceStart'.new InterleavedBufferAttribute( instanceBuffer, 3.0));// xyz
		this.setAttribute( 'instanceEnd'.new InterleavedBufferAttribute( instanceBuffer, 3.3));// xyz

		this.computeBoundingBox();
		this.computeBoundingSphere();
		return this; }}Copy the code

Therefore, when drawing, we only need to call the setPositions method to update the line vertices. At the same time, we need to set the maximum number of vertices that the drawing line can hold in advance, and then control the rendering range.

const MaxCount = 10;
const positions = new Float32Array(MaxCount * 3);
const points = [];

const material = new THREE.LineMaterial({
  linewidth: 2.color: 0xffffff.resolution: new THREE.Vector2(800.600)}); geometry =new THREE.LineGeometry();
geometry.setPositions(positions);
geometry.instanceCount = 0;
line = new THREE.Line2(geometry, material);
line.computeLineDistances();
scene.add(line);

// Update the line when mouse moves or clicks
function updateLine() {
  positions[count * 3 - 3] = mouse.x;
  positions[count * 3 - 2] = mouse.y;
  positions[count * 3 - 1] = mouse.z;
  geometry.setPositions(positions);
  geometry.instanceCount = count - 1;
}
Copy the code

Click and Interaction

How to realize click-select interaction in 3D scene? The screen where the mouse is located is a two-dimensional world, while the screen presents a three-dimensional world. First of all, explain the relationship between the three coordinate systems: world coordinate system, screen coordinate system and viewpoint coordinate system.

  • Scene coordinate system (world coordinate system)

    ThreeJS builds scenes with a fixed coordinate system (no matter where the camera is located), and any objects placed in ThreeJS will be positioned in this coordinate system (0,0,0). For example, let’s create a scene and add arrow assist.

  • Screen coordinates

    The coordinates on the screen are the screen coordinates. As shown in the figure below, the clientX and clientY by one of the most value, the window. The innerWidth, window. The innerHeight decision.

  • Eye position

    Point coordinate system is the center of the camera as the origin, but the position of the camera, also according to the world coordinate system to offset, WebGL will world coordinate transformation to the eye position first, then cut, only within the scope of line of sight (visibility) scenario will enter the next phase of computing guides below to add the camera.

If you want to get the coordinates of the mouse click, you need to convert the screen coordinate system to the scene coordinate system in ThreeJS. One uses geometric intersectionality calculations to emit a ray from the point of view of a mouse click. The geometric intersection between ray and 3d model is used to determine whether the object is picked up. ThreeJS has a built-in class called Raycaster, which gives us a ray that we can then shoot in different directions to see if we’ve hit something, based on whether it’s blocked or not. How do you use the Raycaster class to highlight mouse clicks

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
renderer.domElement.addEventListener("mousedown".(event) = > {
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(cubes, true);
    if (intersects.length > 0) {
        var obj = intersects[0].object;
        obj.material.color.set("#ff0000");
        obj.material.needsUpdate= true; }})Copy the code

Instantiate the Raycaster object and a two-dimensional vector mouse that records the position of the mouse. When the mouseDown event is triggered, the mouse position on the current DOM can be retrieved in the event callback (event.clientx, event.clienty). The screen coordinates are then converted to screen coordinate positions in the scene coordinate system. The corresponding relationship is shown in the following figure.

The origin of the screen coordinate system is the upper left corner and the Y-axis is down, while the origin of the THREE-DIMENSIONAL coordinate system is the center of the screen and the Y-axis is up and normalized. Therefore, if we want to convert the mouse position x to the THREE-DIMENSIONAL coordinate system:

1. Rotate the origin to the center of the screen, i.e. X-0.5 *canvasWidth 2. Normalizing (x-0.5 *canvasWidth)/(0.5*canvasWidth) is the final (Event.clientx/window.innerWidth) * 2-1;Copy the code

I did the same thing on the Y-axis, but I flipped it.

Calling the setFromCamera method on RayCaster gives you a ray that is emitted from the mouse point in line with the camera’s orientation. Then call intersecting object detection function intersecting ray.

class Raycaster {
  // ...intersectObjects(objects: Object3D[], recursive? : boolean, optionalTarget? : Intersection[]): Intersection[]; }Copy the code

The first parameter objects detects a set of objects that intersect the ray, and the second parameter recursive defaults to detecting only objects at the current level. If you need to check all descendants, display the setting to true.

  • Interaction limits in drawing lines

In the line drawing scene, click two points to determine a straight line. However, when viewing the THREE-DIMENSIONAL world in a TWO-DIMENSIONAL screen, the three-dimensional coordinates felt by people are not necessarily the actual three-dimensional coordinates. If the line drawing interaction needs to be more accurate, that is, to ensure that the point clicked by the mouse is the three-dimensional coordinate point understood by the user, some restrictions need to be added.

Since a point can be precisely positioned on a two-dimensional screen, what if we limited the ray pickup to a fixed plane? That is, first determine the plane, then determine the position of the point. You can switch planes before moving on to the next point drawing. By limiting the pickup range, ensure that the mouse click point is the user’s understanding of the THREE-DIMENSIONAL coordinate point.

For simplicity, we create three base pick planes XY/XZ/YZ, pick planes are determined when a point is drawn, and auxiliary grid lines are created to help the user see which plane they are drawn in.

const planeMaterial = new THREE.MeshBasicMaterial();
const planeGeometry = new THREE.PlaneGeometry(100.100);
// The XY plane is drawn in the Z direction
const planeXY = new THREE.Mesh(planeGeometry, planeMaterial);
planeXY.visible = false;
planeXY.name = "planeXY";
planeXY.rotation.set(0.0.0);
scene.add(planeXY);
// The XZ plane is drawn in the Y direction
const planeXZ = new THREE.Mesh(planeGeometry, planeMaterial);
planeXZ.visible = false;
planeXZ.name = "planeXZ";
planeXZ.rotation.set(-Math.PI / 2.0.0);
scene.add(planeXZ);
// The YZ plane is drawn in the X direction
const planeYZ = new THREE.Mesh(planeGeometry, planeMaterial);
planeYZ.visible = false;
planeYZ.name = "planeYZ";
planeYZ.rotation.set(0.Math.PI / 2.0);
scene.add(planeYZ);

// Secondary grid
const grid = new THREE.GridHelper(10.10);
scene.add(grid);

// Initialize the Settings
mode = "XZ";
grid.rotation.set(0.0.0);
activePlane = planeXZ;// Set the pickup plane
Copy the code
  • Update the position as the mouse moves

When the mouse moves, use ray to obtain the coordinates of the mouse point and the pick plane as the position of the next point of the line:

function handleMouseMove(event) {
  if (drawEnabled) {
    const { clientX, clientY } = event;
    const rect = container.getBoundingClientRect();
    mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -(((clientY - rect.top) / rect.height) * 2) + 1;

    raycaster.setFromCamera(mouse, camera);
		// Calculate the intersection of the ray with the current plane
    const intersects = raycaster.intersectObjects([activePlane], true);

    if (intersects.length > 0) {
      const intersect = intersects[0];

      const { x: x0, y: y0, z: z0 } = lastPoint;
      const x = Math.round(intersect.point.x);
      const y = Math.round(intersect.point.y);
      const z = Math.round(intersect.point.z);
      const newPoint = new THREE.Vector3();

      if (mode === "XY") {
        newPoint.set(x, y, z0);
      } else if (mode === "YZ") {
        newPoint.set(x0, y, z);
      } else if (mode === "XZ") { newPoint.set(x, y0, z); } mouse.copy(newPoint); updateLine(); }}}Copy the code
  • Add point when mouse click

After the mouse click, the current point is officially added to the line and recorded as the previous vertex, while updating the position of the pick plane and the secondary grid.

function handleMouseClick() {
  if (drawEnabled) {
    const { x, y, z } = mouse;
    positions[count * 3 + 0] = x;
    positions[count * 3 + 1] = y;
    positions[count * 3 + 2] = z;
    count += 1; grid.position.set(x, y, z); activePlane.position.set(x, y, z); lastPoint = mouse.clone(); }}Copy the code
  • Keyboard switching mode

For convenience, monitor keyboard events to control mode, X/Y/Z switch to different pick planes, D/S to control whether line drawing can be operated.

function handleKeydown(event) {
  if (drawEnabled) {
    switch (event.key) {
      case "d":
        drawEnabled = false;
        break;
      case "s":
        drawEnabled = true;
        break;
      case "x":
        mode = "YZ";
        grid.rotation.set(-Math.PI / 2.0.0);
        activePlane = planeYZ;
        break;
      case "y":
        mode = "XZ";
        grid.rotation.set(0.0.0);
        activePlane = planeXZ;
        break;
      case "z":
        mode = "XY";
        grid.rotation.set(0.0.Math.PI / 2);
        activePlane = planeXY;
        break;
      default:}}}Copy the code

The effect of the final realization

Demo

If you extend it a little bit, you can optimize the interaction in more detail, and you can edit the properties of the wire after it’s generated, and you can do a lot more.

conclusion

Line has always been a very interesting topic in graphic drawing, and there are many technical points that can be extended. From the basic line connection method in OpenGL, to add some width, color and other effects for the line, and how to achieve the line drawing function in the editing scene. If you have any questions about ThreeJS midline summary, please join us!

Author: ES2049 | Dell

The article can be reproduced at will, but please keep the original link.

You are welcome to join ES2049 Studio. Please send your resume to [email protected].