-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathutils_three.ts
170 lines (141 loc) · 5.19 KB
/
utils_three.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import * as THREE from "three";
import { COLORS } from "../enums";
import { AtomMesh, AtomObject } from "./types/atoms";
/**
* TODO: import from a shared utils file
* Converts radians to degrees
*/
export function radiansToDegrees(radians: number): number {
return radians * (180 / Math.PI);
}
export function getArrayFromVector(vector: THREE.Vector3): number[] {
return [vector.x, vector.y, vector.z];
}
export function getObjectCoordinate(object: THREE.Object3D): THREE.Vector3 {
return new THREE.Vector3().setFromMatrixPosition(object.matrixWorld);
}
export function getObjectCoordinateAsArray(object: THREE.Object3D): number[] {
const position = getObjectCoordinate(object);
return getArrayFromVector(position);
}
/**
* Gets the world position of an atom, accounting for repetition
*/
export function getAtomWorldPosition(atom: THREE.Object3D): THREE.Vector3 {
const position = new THREE.Vector3();
// If we have a cached world position (for repeated atoms), use it
if (atom.userData && atom.userData.worldPosition) {
position.copy(atom.userData.worldPosition);
} else {
atom.getWorldPosition(position);
}
return position;
}
/**
* Calculates the angle between three points in 3D space
*/
export function calculateAngleBetweenPoints(
pointA: THREE.Vector3,
pointB: THREE.Vector3,
pointC: THREE.Vector3,
): number {
const vecA = new THREE.Vector3().subVectors(pointA, pointB);
const vecC = new THREE.Vector3().subVectors(pointC, pointB);
const angleRadians = vecA.angleTo(vecC);
return radiansToDegrees(angleRadians);
}
/**
* Calculates angle between three atoms
*/
export function calculateAngleBetweenAtoms(atoms: THREE.Object3D[]): number {
const [firstAtom, centerAtom, lastAtom] = atoms;
const firstPos = getObjectCoordinate(firstAtom);
const centerPos = getObjectCoordinate(centerAtom);
const lastPos = getObjectCoordinate(lastAtom);
return parseFloat(calculateAngleBetweenPoints(firstPos, centerPos, lastPos).toFixed(2));
}
/**
* Calculates the distance between two points
*/
export function calculateDistance(pointA: THREE.Vector3, pointB: THREE.Vector3): number {
return pointA.distanceTo(pointB);
}
/**
* Calculates distance between two atoms
*/
export function calculateDistanceBetweenAtoms(
atomA: THREE.Object3D,
atomB: THREE.Object3D,
): number {
const pointA = getObjectCoordinate(atomA);
const pointB = getObjectCoordinate(atomB);
return calculateDistance(pointA, pointB);
}
/**
* Calculates the midpoint between two points
*/
export function calculateMidpoint(pointA: THREE.Vector3, pointB: THREE.Vector3): THREE.Vector3 {
return new THREE.Vector3().addVectors(pointA, pointB).multiplyScalar(0.5);
}
/**
* Creates a position for a label at an angle between three points
*/
export function calculateAngleLabelPosition(
[firstAtom, centerAtom, thirdAtom]: THREE.Object3D[],
offsetDistance = 0.75,
): THREE.Vector3 {
const centerPos = getObjectCoordinate(centerAtom);
const firstPos = getObjectCoordinate(firstAtom);
const thirdPos = getObjectCoordinate(thirdAtom);
// Create vectors from center to first and third points
const vecFirst = new THREE.Vector3().subVectors(firstPos, centerPos).normalize();
const vecThird = new THREE.Vector3().subVectors(thirdPos, centerPos).normalize();
// Calculate the bisector
const bisector = new THREE.Vector3().addVectors(vecFirst, vecThird).normalize();
// Position on the bisector at the given distance
return centerPos.clone().add(bisector.multiplyScalar(offsetDistance));
}
export function isIntersectionObjectAnAtom(intersection: THREE.Intersection) {
return intersection.object.type === "Mesh";
}
/**
* Sets or resets the color for an atom by modifying its material properties.
* If no color is provided, it restores the previous color and removes emissive effects.
*/
export function setColorForAtom(atom: THREE.Object3D, color?: number): void {
if (!(atom instanceof THREE.Mesh)) return;
const atomMesh = atom as AtomMesh;
const material = atomMesh.material as THREE.MeshStandardMaterial;
if (!material) return;
material.emissive.setHex(color ?? COLORS.BLACK);
if (!color && atomMesh.previousColor) {
material.color.copy(atomMesh.previousColor);
} else if (color && !atomMesh.previousColor) {
atomMesh.previousColor = material.color.clone();
}
}
/**
* Highlights an atom with the specified color.
*/
export function highlightAtom(atom: THREE.Object3D, color: number = COLORS.RED): void {
setColorForAtom(atom, color);
}
/**
* Sets an atom as hovered with a color.
*/
export function setAtomAsHovered(atom: THREE.Object3D): void {
const atomObject = atom as AtomObject;
atomObject.userData.hovered = true;
setColorForAtom(atom, COLORS.RED);
}
/**
* Unsets an atom as hovered, restoring its previous color and removing the emissive effect.
*/
export function unsetAtomAsHovered(atom: THREE.Object3D): void {
const atomObject = atom as AtomObject;
atomObject.userData.hovered = false;
setColorForAtom(atom);
}
export function isObjectAnAtom(object: THREE.Object3D): object is AtomObject {
return object instanceof THREE.Mesh;
}