Skip to content

Commit

Permalink
Merge pull request #27 from andrewisen-tikab/feature
Browse files Browse the repository at this point in the history
Add `LabelsManager`
  • Loading branch information
andrewisen-tikab authored Jul 19, 2023
2 parents 206f7e9 + fdc136a commit b6deb0f
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 34 deletions.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You can create a shape from a set of points and build either a:
- area
- volume

<img src="https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/example.gif" width="100%" />
<img src="https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/example.gif?raw=true" width="100%" />

## Installation

Expand All @@ -28,10 +28,24 @@ const shape = new Shape3D();
scene.add(shape);
```

## API
### Line

TODO

### Area

TODO

### Volume

TODO

### Labels

TODO

<img src="https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/labels.gif?raw=true" width="100%" />

## Example

See the [example](./example) folder for an example.
Expand All @@ -53,23 +67,23 @@ This is a work in progress. It's not ready for production use.

The design is based on the following:

![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-01.png 'Design')
![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-01.png?raw=true 'Design')

A line is defined by a set of 3-dimensional vertices. The point between two vertices is called `midpoint`.

![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-02.png 'Design')
![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-02.png?raw=true 'Design')

The first vertex is called the `startpoint` and the last is called the `endpoint`.

![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-03.png 'Design')
![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-03.png?raw=true 'Design')

A line can be closed by setting `closeLine` to `true`.

![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-04.png 'Design')
![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-04.png?raw=true 'Design')

An area is a line with three or more vertices.

![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-05.png 'Design')
![Design](https://github.com/andrewisen-tikab/three-shape-3d/blob/feature/resources/definition-05.png?raw=true 'Design')

A volume is an area with a positive height.

Expand Down
Binary file added resources/labels.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 158 additions & 0 deletions src/controls/TransformShapeControls/LabelsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import * as THREE from 'three';
import type { TransformShapeControls } from './TransformShapeControls';
import { Vertex } from '../../types';
import { getLength2D, getMidpointOffsetFromLine } from '../../utils';
import { addPrefix } from './labels';
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';

/**
* Manages the labels for the {@link TransformShapeControls}.
*/
export default class LabelsManager extends THREE.EventDispatcher {
private transformShapeControls: TransformShapeControls;

private labels: CSS2DObject[] = [];

constructor(transformShapeControls: TransformShapeControls) {
super();
this.transformShapeControls = transformShapeControls;
this.labels = [];
}

/**
* Call this every frame.
*/
public update() {
this.updatePositions();
}

/**
* Add new labels to the object.
*/
public addLabels() {
if (!this.transformShapeControls.object) return;
this.labels = [];
const vertices = this.transformShapeControls.object.getVertices();

// Get distance from camera to center of object
const offsetDistance = 1;

const center: Vertex = [
this.transformShapeControls.vertexCenter.x,
this.transformShapeControls.vertexCenter.y,
this.transformShapeControls.vertexCenter.z,
];
for (let index = 0; index < vertices.length; index++) {
const vertex = vertices[index];
if (index === 0) continue;
const previousVertex = vertices[index - 1];
const label = this.generateLabel(
this.transformShapeControls.labelsGroup,
index,
vertex,
previousVertex,
center,
offsetDistance,
);
this.labels.push(label);
}

if (this.transformShapeControls.object!.getCloseLine()) {
const vertex = vertices[0];
const previousVertex = vertices[vertices.length - 1];

const label = this.generateLabel(
this.transformShapeControls.labelsGroup,
vertices.length,
vertex,
previousVertex,
center,
offsetDistance,
);
this.labels.push(label);
}
}

/**
* Updates the position of the labels based on the camera's distance.
*/
private updatePositions(): void {
if (!this.transformShapeControls.object) return;

const vertices = this.transformShapeControls.object.getVertices();
const offsetDistance =
this.transformShapeControls.vertexCenter.distanceTo(
this.transformShapeControls.camera.position,
) /
100 +
1.1;

const center: Vertex = [
this.transformShapeControls.vertexCenter.x,
this.transformShapeControls.vertexCenter.y,
this.transformShapeControls.vertexCenter.z,
];
for (let i = 0; i < this.labels.length; i++) {
const label = this.labels[i];
const firstVertex = vertices[i];
const secondVertex = i === vertices.length - 1 ? vertices[0] : vertices[i + 1];
this.updatePosition(label, firstVertex, secondVertex, center, offsetDistance);
}
}

private updatePosition(
label: CSS2DObject,
firstVertex: Vertex,
secondVertex: Vertex,
center: Vertex,
offsetDistance: number,
) {
const offset = getMidpointOffsetFromLine(firstVertex, secondVertex, center, offsetDistance);
label.position.set(offset[0], offset[1], offset[2]);
}

private generateLabel(
parent: THREE.Object3D,
index: number,
firstVertex: Vertex,
secondVertex: Vertex,
center: Vertex,
offsetDistance: number,
): CSS2DObject {
const length = getLength2D(firstVertex, secondVertex);

const divElement = document.createElement('div');
divElement.className = 'shape-3d-label-container';

const inputElement = document.createElement('input');
inputElement.type = 'text';

inputElement.id = 'myInput';
inputElement.className = 'shape-3d-label';
inputElement.placeholder = `${length.toFixed(2)}`;
inputElement.setAttribute('size', `${inputElement.getAttribute('placeholder')!.length}`);
inputElement.oninput = addPrefix.bind(inputElement);

inputElement.addEventListener('blur', (e) => {
this.onBlur(e, index);
});

divElement.appendChild(inputElement);
const label = new CSS2DObject(divElement);
this.updatePosition(label, firstVertex, secondVertex, center, offsetDistance);
parent.add(label);

return label;
}

onBlur(e: FocusEvent, index: number) {
// Get value of input
const inputElement = e.target as HTMLInputElement;
const value = inputElement.value;

const number = parseFloat(value);
if (isNaN(number)) return;
if (number <= 0) return;
this.transformShapeControls.setLineLength(index, number);
}
}
57 changes: 31 additions & 26 deletions src/controls/TransformShapeControls/TransformShapeControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;

import Shape3D from '../../Shape3D';
import { getMidpoint } from '../../utils';
import { generateLabel } from './labels';
import { Vertex } from '../../types';
import { getMidpoint, setLineLength } from '../../utils';
import LabelsManager from './LabelsManager';

const _raycaster = new THREE.Raycaster();
// @ts-ignore
Expand Down Expand Up @@ -50,9 +49,11 @@ type VertexMetadata = {
type Mode = 'translate' | 'rotate' | 'scale';

class TransformShapeControls extends THREE.Object3D {
private vertexGroup!: THREE.Group;
public vertexGroup!: THREE.Group;

private labelsGroup!: THREE.Group;
public labelsGroup!: THREE.Group;

private labelsManager: LabelsManager;

public object?: Shape3D;

Expand Down Expand Up @@ -149,7 +150,7 @@ class TransformShapeControls extends THREE.Object3D {
private _onPointerUp!: (event: any) => void;
params: Partial<TransformShapeControlsGizmoParams>;

private vertexCenter: THREE.Vector3;
public vertexCenter: THREE.Vector3;

private lastSelectedVertex: THREE.Mesh | null = null;
private lastSelectedVertexQuaternion: THREE.Quaternion;
Expand Down Expand Up @@ -263,6 +264,7 @@ class TransformShapeControls extends THREE.Object3D {
this.vertexHoverMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
this.add(vertexGroup);

this.labelsManager = new LabelsManager(this);
const labelsGroup = new THREE.Group();
this.add(labelsGroup);

Expand Down Expand Up @@ -429,6 +431,8 @@ class TransformShapeControls extends THREE.Object3D {
this.eye.copy(this.cameraPosition).sub(this.worldPosition).normalize();
}

this.labelsManager.update();

// @ts-ignore
super.updateMatrixWorld(this);
}
Expand Down Expand Up @@ -949,26 +953,7 @@ class TransformShapeControls extends THREE.Object3D {

private addLabels() {
this.labelsGroup.clear();
const vertices = this.object!.getVertices();

// Get distance from camera to center of object
const offsetDistance = this.vertexCenter.distanceTo(this.camera.position) / 100 + 1.1;

const center: Vertex = [this.vertexCenter.x, this.vertexCenter.y, this.vertexCenter.z];
for (let index = 0; index < vertices.length; index++) {
const vertex = vertices[index];
if (index === 0) continue;
const previousVertex = vertices[index - 1];
generateLabel(this.labelsGroup, vertex, previousVertex, center, offsetDistance);
}

if (this.object!.getCloseLine()) {
const vertex = vertices[0];
const previousVertex = vertices[vertices.length - 1];

generateLabel(this.labelsGroup, vertex, previousVertex, center, offsetDistance);
}

this.labelsManager.addLabels();
this.labelsGroup.position.set(-this.position.x, -this.position.y, -this.position.z);
}

Expand Down Expand Up @@ -1040,6 +1025,26 @@ class TransformShapeControls extends THREE.Object3D {
setSpace(space: string) {
this.space = space;
}

setLineLength(index: number, lineLength: number) {
if (this.object == null) {
console.warn('No object attached');
return;
}
if (index < 0 || index > this.object!.getVertices().length) {
console.warn('Invalid index');
return;
}
if (lineLength < 0) {
console.warn('Invalid line length');
return;
}
const adjustedIndex = index === this.object!.getVertices().length ? 0 : index;
const vertices = this.object!.getVertices();
const newVertex = setLineLength(index, lineLength, vertices);
this.object.updateVertex(adjustedIndex, newVertex);
this.onVertexChanged();
}
}

// mouse / touch event handlers
Expand Down
10 changes: 9 additions & 1 deletion src/controls/TransformShapeControls/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
import { Vertex } from '../../types';
import { getLength2D, getMidpointOffsetFromLine } from '../../utils';

function addPrefix(this: HTMLInputElement, _ev: Event) {
export function addPrefix(this: HTMLInputElement, _ev: Event) {
this.setAttribute('size', `${this.value!.length}`);
}

/**
* @deprecated Use {@link LabelsManager} instead.
* @param parent
* @param firstVertex
* @param secondVertex
* @param center
* @param offsetDistance
*/
export const generateLabel = (
parent: THREE.Object3D,
firstVertex: Vertex,
Expand Down
14 changes: 14 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,17 @@ export const getLength2D = (firstVertex: Vertex, secondVertex: Vertex): number =
Math.pow(firstVertex[2] - secondVertex[2], 2),
);
};

export const setLineLength = (index: number, lineLength: number, vertices: Vertex[]): Vertex => {
const closedLine = vertices.length === index;

const firstVertex = closedLine ? vertices[vertices.length - 1] : vertices[index - 1];
const secondVertex = closedLine ? vertices[0] : vertices[index];

_firstVertex.fromArray(firstVertex);
_secondVertex.fromArray(secondVertex);

_line.subVectors(_secondVertex, _firstVertex).normalize();
_firstVertex.add(_line.multiplyScalar(lineLength));
return _firstVertex.toArray();
};

0 comments on commit b6deb0f

Please sign in to comment.