Skip to content

Commit

Permalink
Merge pull request #1 from superstar54/fix_transform
Browse files Browse the repository at this point in the history
    - fix currentFrame in operations
    - fix viewRect in transform
    - create custom Scene and OrthographicCamera
  • Loading branch information
superstar54 committed Mar 24, 2024
1 parent b7cdca2 commit 7481a85
Show file tree
Hide file tree
Showing 49 changed files with 412 additions and 193 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## Web Environment For Atomistic Structure (WEAS)
## Web Environment For Atomic Structure (WEAS)

[![npm version](https://img.shields.io/npm/v/weas.svg?style=flat-square)](https://www.npmjs.com/package/weas)
[![Docs status](https://readthedocs.org/projects/weas/badge)](http://weas.readthedocs.io/)
[![Unit test](https://github.com/superstar54/weas/actions/workflows/ci.yml/badge.svg)](https://github.com/superstar54/weas/actions/workflows/ci.yml)

The WEAS package is a JavaScript library designed to visualize and edit atomistic structures (molecule, crystal, nanoparticle) in the web environments.
The WEAS package is a JavaScript library designed to visualize and edit atomic structures (molecule, crystal, nanoparticle) in the web environments.

Features:

Expand Down Expand Up @@ -60,6 +60,12 @@ npm run build
npx playwright test
```

Run the test with the title

```
npx playwright test -g "Animation"
```

If the snapshots need to be updated:

```
Expand Down
2 changes: 2 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { WEAS, Atoms, Species, parseXYZ, parseCIF, parseCube } from "../src/index.js"; // Adjust the path as necessary
import * as THREE from "three";

window.THREE = THREE;
window.WEAS = WEAS;
window.Atoms = Atoms;
window.Species = Species;
Expand Down
1 change: 1 addition & 0 deletions examples/h2o.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://unpkg.com/weas/dist/style.css" />
<title>WEAS Molecule</title>
</head>
<body>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "weas",
"version": "0.1.0",
"version": "0.1.1",
"description": "WEAS (Web Environment for Atomic Structures) is a JavaScript library to visualize and manipulate the atomistic structures directly in the web browser",
"main": "src/index.js",
"scripts": {
Expand Down
30 changes: 25 additions & 5 deletions src/atoms/AtomsViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,10 @@ class AtomsViewer {
}
this.atomColors = getAtomColors(this.atoms, this.colorBy, { colorType: this.colorType, colorRamp: this._colorRamp });
this.drawBalls();
this.bondManager.drawBonds();
this.polyhedraManager.drawPolyhedras();
const bondMesh = this.bondManager.drawBonds();
this.atomsMesh.add(bondMesh);
const polyhedraMesh = this.polyhedraManager.drawPolyhedras();
this.atomsMesh.add(polyhedraMesh);
this.isosurfaceManager.drawIsosurfaces();
if (this.showVectorField) {
this.VFManager.drawVectorFields();
Expand All @@ -459,6 +461,7 @@ class AtomsViewer {
drawBalls() {
// draw atoms
this.atomsMesh = drawAtoms({ scene: this.tjs.scene, atoms: this.atoms, atomScales: this.atomScales, colors: this.atomColors, radiusType: this.radiusType, materialType: this._materialType });
this.tjs.scene.add(this.atomsMesh);
// atoms to be drawn, boundary atoms, and the bonded atoms
// merge the boundaryList and the bondedAtoms
this.imageAtomsList = this.bondedAtoms["atoms"].concat(this.boundaryList);
Expand All @@ -473,7 +476,16 @@ class AtomsViewer {
atomScales[i] = this.atomScales[this.imageAtomsList[i][0]];
}
const atomColors = getAtomColors(imageAtomsList, this.colorBy, { colorType: this.colorType, defaultColor: "#0xffffff", colorRamp: this._colorRamp });
this.boundaryAtomsMesh = drawAtoms({ scene: this.tjs.scene, atoms: imageAtomsList, atomScales: atomScales, colors: atomColors, radiusType: this.radiusType, materialType: this._materialType });
this.boundaryAtomsMesh = drawAtoms({
scene: this.tjs.scene,
atoms: imageAtomsList,
atomScales: atomScales,
colors: atomColors,
radiusType: this.radiusType,
materialType: this._materialType,
data_type: "boundary",
});
this.atomsMesh.add(this.boundaryAtomsMesh);
}
}

Expand All @@ -482,8 +494,16 @@ class AtomsViewer {
const atomScales = new Array(this.atoms.getAtomsCount()).fill(0);
// use yellow color to highlight the selected atoms
const atomColors = new Array(this.atoms.getAtomsCount()).fill(new THREE.Color(0xffff00));
this.highlightAtomsMesh = drawAtoms({ scene: this.tjs.scene, atoms: this.atoms, atomScales: atomScales, colors: atomColors, radiusType: this.radiusType, materialType: "Basic" });
this.tjs.scene.add(this.highlightAtomsMesh);
this.highlightAtomsMesh = drawAtoms({
scene: this.tjs.scene,
atoms: this.atoms,
atomScales: atomScales,
colors: atomColors,
radiusType: this.radiusType,
materialType: "Basic",
data_type: "highlight",
});
this.atomsMesh.add(this.highlightAtomsMesh);
this.highlightAtomsMesh.material.opacity = 0.6;
this.updateHighlightAtomsMesh(this.selectedAtomsIndices);
}
Expand Down
5 changes: 2 additions & 3 deletions src/atoms/draw_atoms.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { materials } from "../tools/materials.js";

const Radii = { Covalent: covalentRadii, VDW: vdwRadii };

export function drawAtoms({ scene, atoms, atomScales, colors, radiusType = "Covalent", materialType = "Standard" }) {
export function drawAtoms({ atoms, atomScales, colors, radiusType = "Covalent", materialType = "Standard", data_type = "atom" }) {
console.time("drawAtoms Time");
// console.log("atomScales: ", atomScales);
console.log("Draw Atoms: ", +atoms.symbols.length, " atoms");
Expand Down Expand Up @@ -45,7 +45,7 @@ export function drawAtoms({ scene, atoms, atomScales, colors, radiusType = "Cova
// Set color
instancedMesh.setColorAt(globalIndex, colors[globalIndex]);
});
instancedMesh.userData.type = "atom";
instancedMesh.userData.type = data_type;
instancedMesh.userData.uuid = atoms.uuid;
// the default objectMode for atoms is "edit"
instancedMesh.userData.objectMode = "edit";
Expand All @@ -57,7 +57,6 @@ export function drawAtoms({ scene, atoms, atomScales, colors, radiusType = "Cova
instancedMesh.instanceColor.needsUpdate = true;
}

scene.add(instancedMesh);
console.timeEnd("drawAtoms Time");
return instancedMesh;
}
2 changes: 1 addition & 1 deletion src/atoms/plugins/bond.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class BondManager {
atomColors = this.viewer.atomColors;
}
this.bondMesh = drawStick(this.viewer.atoms, this.bondList, this.buildBondDict(), this.viewer.bondRadius, this.viewer._materialType, atomColors);
this.scene.add(this.bondMesh);
return this.bondMesh;
}

updateBondMesh(atomIndex = null, atoms = null) {
Expand Down
1 change: 1 addition & 0 deletions src/atoms/plugins/isosurface.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class Isosurface {
drawIsosurfaces() {
/* Draw isosurfaces */
if (this.volumetricData === null) {
console.log("No volumetric data is set");
return;
}
console.log("drawIsosurfaces");
Expand Down
14 changes: 7 additions & 7 deletions src/atoms/plugins/polyhedra.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export class PolyhedraManager {
this.viewer = viewer;
this.scene = this.viewer.tjs.scene;
this.settings = [];
this.meshes = [];
// create a group to store the polyhedra meshes
this.meshes = new THREE.Group();
this.init();
}

Expand Down Expand Up @@ -81,9 +82,7 @@ export class PolyhedraManager {

clearMeshes() {
/* Remove highlighted atom meshes from the selectedAtomsMesh group */
this.meshes.forEach((mesh) => {
clearObject(this.scene, mesh);
});
clearObject(this.scene, this.meshes);
}

drawPolyhedras() {
Expand All @@ -92,10 +91,11 @@ export class PolyhedraManager {
if (this.viewer.debug) {
console.log("polyhedras: ", polyhedras);
}
this.meshes = drawPolyhedras(this.viewer.atoms, polyhedras, this.viewer.bondManager.bondList, this.viewer._colorType, this.viewer._materialType);
this.meshes.forEach((mesh) => {
this.scene.add(mesh);
const meshes = drawPolyhedras(this.viewer.atoms, polyhedras, this.viewer.bondManager.bondList, this.viewer._colorType, this.viewer._materialType);
meshes.forEach((mesh) => {
this.meshes.add(mesh);
});
return this.meshes;
}

updatePolyhedraMesh(atomIndex = null, atoms = null) {
Expand Down
42 changes: 16 additions & 26 deletions src/controls/TransformControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export class TransformControls {
this.centroidNDC = new THREE.Vector2();
this.initialAtomPositions = new Map(); // To store initial positions of selected atoms
this.initialObjectState = new Map(); // To store initial state of selected objects
this.viewerRect = this.tjs.containerElement.getBoundingClientRect();
// Get the camera's forward direction (negative z-axis in world space)
this.cameraDirection = new THREE.Vector3(0, 0, -1);
}
Expand All @@ -43,6 +42,7 @@ export class TransformControls {
enterMode(mode, mousePosition) {
this.mode = mode;
console.log("Enter mode: ", this.mode);
this.cameraDirection = new THREE.Vector3(0, 0, -1);
this.cameraDirection.applyQuaternion(this.tjs.camera.quaternion);
if (this.mode === "translate") {
// Get the camera's forward direction (negative z-axis in world space)
Expand Down Expand Up @@ -74,17 +74,15 @@ export class TransformControls {
// Create a translate operation
if (this.mode === "translate") {
const translateVector = this.getTranslateVector(this.eventHandler.currentMousePosition, this.initialMousePosition);
console.log("Translate vector: ", translateVector);
const translateOperation = new TranslateOperation(this.weas, translateVector);
const translateOperation = new TranslateOperation({ weas: this.weas, vector: translateVector });
this.weas.ops.execute(translateOperation, false);
} else if (this.mode === "rotate") {
const rotationAngle = this.getRotationAngle(this.eventHandler.currentMousePosition, this.initialMousePosition);
const rotateOperation = new RotateOperation(this.weas, this.cameraDirection, rotationAngle);
const rotateOperation = new RotateOperation({ weas: this.weas, axis: this.cameraDirection, angle: rotationAngle });
this.weas.ops.execute(rotateOperation, false);
} else if (this.mode === "scale") {
const scaleVector = this.getScaleVector(this.eventHandler.currentMousePosition, this.initialMousePosition);
console.log("Scale vector: ", scaleVector);
const scaleOperation = new ScaleOperation(this.weas, scaleVector);
const scaleOperation = new ScaleOperation({ weas: this.weas, scale: scaleVector });
this.weas.ops.execute(scaleOperation, false);
} else {
console.log("Invalid mode");
Expand Down Expand Up @@ -175,9 +173,15 @@ export class TransformControls {
this.weas.objectManager.translateSelectedObjects(translateVector);
}

getNDC(mousePosition) {
return new THREE.Vector2(((mousePosition.x - this.tjs.viewerRect.left) / this.tjs.viewerRect.width) * 2 - 1, -((mousePosition.y - this.tjs.viewerRect.top) / this.tjs.viewerRect.height) * 2 + 1);
}

getTranslateVector(currentMousePosition, previousMousePosition) {
const currentWorldPosition = getWorldPositionFromScreen(currentMousePosition.x, currentMousePosition.y, this.tjs.camera, this.translatePlane);
const previousWorldPosition = getWorldPositionFromScreen(previousMousePosition.x, previousMousePosition.y, this.tjs.camera, this.translatePlane);
const newNDC = this.getNDC(currentMousePosition);
const currentWorldPosition = getWorldPositionFromScreen(this.tjs.camera, newNDC, this.translatePlane);
const initialNDC = this.getNDC(previousMousePosition);
const previousWorldPosition = getWorldPositionFromScreen(this.tjs.camera, initialNDC, this.translatePlane);
return currentWorldPosition.sub(previousWorldPosition);
}

Expand All @@ -200,15 +204,8 @@ export class TransformControls {
}

getScaleVector(currentMousePosition, previousMousePosition) {
const initialNDC = new THREE.Vector2(
((previousMousePosition.x - this.viewerRect.left) / this.viewerRect.width) * 2 - 1,
-((previousMousePosition.y - this.viewerRect.top) / this.viewerRect.height) * 2 + 1,
);

const newNDC = new THREE.Vector2(
((currentMousePosition.x - this.viewerRect.left) / this.viewerRect.width) * 2 - 1,
-((currentMousePosition.y - this.viewerRect.top) / this.viewerRect.height) * 2 + 1,
);
const initialNDC = this.getNDC(previousMousePosition);
const newNDC = this.getNDC(currentMousePosition);
if (initialNDC.equals(newNDC)) {
return; // Skip further processing
}
Expand All @@ -221,15 +218,8 @@ export class TransformControls {
}

getRotationAngle(currentMousePosition, previousMousePosition) {
const initialNDC = new THREE.Vector2(
((previousMousePosition.x - this.viewerRect.left) / this.viewerRect.width) * 2 - 1,
-((previousMousePosition.y - this.viewerRect.top) / this.viewerRect.height) * 2 + 1,
);

const newNDC = new THREE.Vector2(
((currentMousePosition.x - this.viewerRect.left) / this.viewerRect.width) * 2 - 1,
-((currentMousePosition.y - this.viewerRect.top) / this.viewerRect.height) * 2 + 1,
);
const initialNDC = this.getNDC(previousMousePosition);
const newNDC = this.getNDC(currentMousePosition);
if (initialNDC.equals(newNDC)) {
console.log("No mouse movement detected, skipping rotation.");
return; // Skip further processing
Expand Down
39 changes: 39 additions & 0 deletions src/core/Camera.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as THREE from "three";

export class OrthographicCamera extends THREE.OrthographicCamera {
constructor(left, right, top, bottom, near, far, tjs = null) {
super(left, right, top, bottom, near, far);
this.tjs = tjs;
}

// Custom method to update zoom
updateZoom(value) {
if (this.zoom !== value) {
this.zoom = value;
this.updateProjectionMatrix(); // Required to apply the zoom change
this.dispatchObjectEvent({
data: value,
action: "zoom",
catalog: "camera",
});
}
}

// Custom method to update position
updatePosition(x, y, z) {
const newPos = new THREE.Vector3(x, y, z);
if (!this.position.equals(newPos)) {
this.position.copy(newPos);
this.dispatchObjectEvent({
data: [x, y, z],
action: "position",
catalog: "camera",
});
}
}

dispatchObjectEvent(data) {
const event = new CustomEvent("weas", { detail: data });
this.tjs.containerElement.dispatchEvent(event);
}
}
27 changes: 18 additions & 9 deletions src/core/ObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,42 @@ export class ObjectManager {
constructor(weas) {
this.weas = weas;
this.selectionManager = weas.selectionManager;
this.sceneManager = weas.tjs.sceneManager;
this.scene = weas.tjs.scene;
}

translateSelectedObjects(translateVector) {
this.selectionManager.selectedObjects.forEach((object) => {
translateSelectedObjects(translateVector, selectedObjects = null) {
if (selectedObjects === null) {
selectedObjects = this.selectionManager.selectedObjects;
}
selectedObjects.forEach((object) => {
const initialPosition = object.position.clone();
object.position.copy(initialPosition.add(translateVector));
});
}

rotateSelectedObjects(rotationAxis, rotationAngle) {
rotateSelectedObjects(rotationAxis, rotationAngle, selectedObjects = null) {
if (selectedObjects === null) {
selectedObjects = this.selectionManager.selectedObjects;
}
rotationAxis = rotationAxis.normalize();
rotationAngle = THREE.MathUtils.degToRad(rotationAngle);
this.selectionManager.selectedObjects.forEach((object) => {
selectedObjects.forEach((object) => {
object.rotateOnAxis(rotationAxis, -rotationAngle);
});
}

deleteSelectedObjects() {
this.selectionManager.selectedObjects.forEach((object) => {
clearObject(this.sceneManager.scene, object);
clearObject(this.scene, object);
});
this.selectionManager.clearSelection();
}

scaleSelectedObjects(scale) {
this.selectionManager.selectedObjects.forEach((object) => {
scaleSelectedObjects(scale, selectedObjects = null) {
if (selectedObjects === null) {
selectedObjects = this.selectionManager.selectedObjects;
}
selectedObjects.forEach((object) => {
object.scale.multiply(scale);
});
}
Expand All @@ -41,7 +50,7 @@ export class ObjectManager {
this.selectionManager.selectedObjects.forEach((object) => {
const clone = object.clone();
clone.position.add(new THREE.Vector3(1, 1, 1));
this.sceneManager.scene.add(clone);
this.scene.add(clone);
newObjects.push(clone);
});
this.selectionManager.selectedObjects = newObjects;
Expand Down
Loading

0 comments on commit 7481a85

Please sign in to comment.