diff --git a/README.md b/README.md index 267c909..765d867 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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: ``` diff --git a/demo/demo.js b/demo/demo.js index 96f77bc..66814e6 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -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; diff --git a/examples/h2o.html b/examples/h2o.html index 4e539df..9635a9e 100644 --- a/examples/h2o.html +++ b/examples/h2o.html @@ -2,6 +2,7 @@ + WEAS Molecule diff --git a/package-lock.json b/package-lock.json index f1ed756..55338ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "weas", - "version": "0.0.8-b", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "weas", - "version": "0.0.8-b", + "version": "0.1.1", "license": "MIT", "dependencies": { "dat.gui": "^0.7.9", diff --git a/package.json b/package.json index 42baf73..9bf953b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/atoms/AtomsViewer.js b/src/atoms/AtomsViewer.js index d55faa7..e2e9f50 100644 --- a/src/atoms/AtomsViewer.js +++ b/src/atoms/AtomsViewer.js @@ -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(); @@ -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); @@ -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); } } @@ -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); } diff --git a/src/atoms/draw_atoms.js b/src/atoms/draw_atoms.js index accee9e..599b385 100644 --- a/src/atoms/draw_atoms.js +++ b/src/atoms/draw_atoms.js @@ -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"); @@ -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"; @@ -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; } diff --git a/src/atoms/plugins/bond.js b/src/atoms/plugins/bond.js index c71a34f..ce710fd 100644 --- a/src/atoms/plugins/bond.js +++ b/src/atoms/plugins/bond.js @@ -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) { diff --git a/src/atoms/plugins/isosurface.js b/src/atoms/plugins/isosurface.js index 7b7742d..a490c13 100644 --- a/src/atoms/plugins/isosurface.js +++ b/src/atoms/plugins/isosurface.js @@ -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"); diff --git a/src/atoms/plugins/polyhedra.js b/src/atoms/plugins/polyhedra.js index 1a72518..9fac6e7 100644 --- a/src/atoms/plugins/polyhedra.js +++ b/src/atoms/plugins/polyhedra.js @@ -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(); } @@ -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() { @@ -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) { diff --git a/src/controls/TransformControls.js b/src/controls/TransformControls.js index c298323..20a6b1e 100644 --- a/src/controls/TransformControls.js +++ b/src/controls/TransformControls.js @@ -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); } @@ -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) @@ -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"); @@ -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); } @@ -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 } @@ -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 diff --git a/src/core/Camera.js b/src/core/Camera.js new file mode 100644 index 0000000..eb8c9af --- /dev/null +++ b/src/core/Camera.js @@ -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); + } +} diff --git a/src/core/ObjectManager.js b/src/core/ObjectManager.js index df4f195..b595ddb 100644 --- a/src/core/ObjectManager.js +++ b/src/core/ObjectManager.js @@ -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); }); } @@ -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; diff --git a/src/core/SceneManager.js b/src/core/SceneManager.js index 2515ee0..30378b6 100644 --- a/src/core/SceneManager.js +++ b/src/core/SceneManager.js @@ -3,28 +3,46 @@ import * as THREE from "three"; import { clearObjects } from "../utils.js"; -export class SceneManager { - constructor() { - this.scene = new THREE.Scene(); +export class WeasScene extends THREE.Scene { + constructor(tjs) { + super(); + this.tjs = tjs; } + add(object) { - this.scene.add(object); + console.log("add object", object); + super.add(object); + this.dispatchObjectEvent({ + data: object.toJSON(), + action: "add", + catalog: "object", + }); } + remove(object) { - this.scene.remove(object); + console.log("remove object", object); + // if object is a string, find it by uuid + if (typeof object === "string") { + object = this.getObjectByProperty("uuid", object); + if (!object) { + console.warn("Object not found"); + return; + } + } + super.remove(object); + this.dispatchObjectEvent({ + data: object.toJSON(), + action: "remove", + catalog: "object", + }); } - get children() { - return this.scene.children; - } - // Call this method after updating atoms - dispatchViewerUpdated(data) { - // create a list of picked atoms from the selectedAtomsIndices set - const event = new CustomEvent("viewerUpdated", { detail: data }); + + dispatchObjectEvent(data) { + const event = new CustomEvent("weas", { detail: data }); this.tjs.containerElement.dispatchEvent(event); - console.log("Dispatch viewerUpdated"); } clear() { - clearObjects(this.scene); + clearObjects(this); } } diff --git a/src/core/SelectionManager.js b/src/core/SelectionManager.js index 7025763..9dfa269 100644 --- a/src/core/SelectionManager.js +++ b/src/core/SelectionManager.js @@ -21,7 +21,6 @@ export class SelectionManager { } init() { - this.viewerRect = this.tjs.containerElement.getBoundingClientRect(); this.selectionBox = new SelectionBox(this.tjs.camera, this.tjs.scene); this.helper = new SelectionHelper(this.tjs.renderers["MainRenderer"].renderer, "selectBox"); window.addEventListener("pointerdown", this.onMouseDown.bind(this), false); @@ -42,16 +41,16 @@ export class SelectionManager { } onMouseDown(event) { - let x = ((event.clientX - this.viewerRect.left) / this.viewerRect.width) * 2 - 1; - let y = -((event.clientY - this.viewerRect.top) / this.viewerRect.height) * 2 + 1; + let x = ((event.clientX - this.tjs.viewerRect.left) / this.tjs.viewerRect.width) * 2 - 1; + let y = -((event.clientY - this.tjs.viewerRect.top) / this.tjs.viewerRect.height) * 2 + 1; this.selectionBox.startPoint.set(x, y, 0.5); this.oldSelectedAtomsIndices = this.weas.avr.selectedAtomsIndices; this.oldSelectedObjects = this.selectedObjects; } pickSelection(event) { - this.mouse.x = ((event.clientX - this.viewerRect.left) / this.viewerRect.width) * 2 - 1; - this.mouse.y = -((event.clientY - this.viewerRect.top) / this.viewerRect.height) * 2 + 1; + this.mouse.x = ((event.clientX - this.tjs.viewerRect.left) / this.tjs.viewerRect.width) * 2 - 1; + this.mouse.y = -((event.clientY - this.tjs.viewerRect.top) / this.tjs.viewerRect.height) * 2 + 1; // Update the picking ray this.raycaster.setFromCamera(this.mouse, this.tjs.camera); @@ -135,8 +134,8 @@ export class SelectionManager { } dragSelection(event) { - let x = ((event.clientX - this.viewerRect.left) / this.viewerRect.width) * 2 - 1; - let y = -((event.clientY - this.viewerRect.top) / this.viewerRect.height) * 2 + 1; + let x = ((event.clientX - this.tjs.viewerRect.left) / this.tjs.viewerRect.width) * 2 - 1; + let y = -((event.clientY - this.tjs.viewerRect.top) / this.tjs.viewerRect.height) * 2 + 1; this.selectionBox.endPoint.set(x, y, 0.5); this.selectionBox.select(); diff --git a/src/core/blendjs.js b/src/core/blendjs.js index 4d63584..2d0259d 100644 --- a/src/core/blendjs.js +++ b/src/core/blendjs.js @@ -2,7 +2,8 @@ import * as THREE from "three"; import { OrbitControls } from "../three/OrbitControls.js"; import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js"; -import { SceneManager } from "./SceneManager.js"; +import { WeasScene } from "./SceneManager.js"; +import { OrthographicCamera } from "./Camera.js"; class BlendJSObject { constructor(name, geometry, material) { @@ -44,8 +45,7 @@ class BlendJSRenderer { export class BlendJS { constructor(containerElement) { this.containerElement = containerElement; - this.sceneManager = new SceneManager(); - this.scene = this.sceneManager.scene; + this.scene = new WeasScene(this); this.objects = {}; this.materials = {}; this.meshes = {}; @@ -54,7 +54,7 @@ export class BlendJS { this.init(); } init() { - this.sceneManager.scene.background = new THREE.Color(0xffffff); // Set the scene's background to white + this.scene.background = new THREE.Color(0xffffff); // Set the scene's background to white // Create a renderer const renderer = new THREE.WebGLRenderer(); renderer.setSize(this.containerElement.clientWidth, this.containerElement.clientHeight); @@ -76,13 +76,14 @@ export class BlendJS { const frustumHalfHeight = frustumSize / 2; const frustumHalfWidth = frustumHalfHeight * aspect; - this.camera = new THREE.OrthographicCamera( + this.camera = new OrthographicCamera( -frustumHalfWidth, // left frustumHalfWidth, // right frustumHalfHeight, // top -frustumHalfHeight, // bottom 1, // near clipping plane 2000, // far clipping plane + this, ); // Set initial camera position this.camera.position.set(0, -100, 0); @@ -90,7 +91,7 @@ export class BlendJS { // Enable layer 1 for the camera // this layer will be used for vertex indicators this.camera.layers.enable(1); - this.sceneManager.add(this.camera); + this.scene.add(this.camera); // Create a light const light = new THREE.DirectionalLight(0xffffff, 2.0); light.position.set(50, 50, 100); @@ -108,13 +109,14 @@ export class BlendJS { // this.controls.enablePan = true; // This line disables panning // this.controls.enableDamping = true; // Enable smooth camera movements // Add event listener for window resize + this.viewerRect = this.containerElement.getBoundingClientRect(); window.addEventListener("resize", this.onWindowResize.bind(this), false); } addObject(name, geometry, material) { const object = new BlendJSObject(name, geometry, material); this.objects[name] = object; - this.sceneManager.add(object.object3D); + this.scene.add(object.object3D); return object; } @@ -134,7 +136,7 @@ export class BlendJS { addLight(name, light) { const lgt = new BlendJSLight(name, light); this.lights[name] = lgt; - this.sceneManager.add(lgt.light); + this.scene.add(lgt.light); return lgt; } @@ -161,21 +163,23 @@ export class BlendJS { Object.values(this.renderers).forEach((rndr) => { rndr.renderer.setSize(this.containerElement.clientWidth, this.containerElement.clientHeight); }); + this.viewerRect = this.containerElement.getBoundingClientRect(); } // - updateCameraAndControls({ center = null, direction = [0, 0, 1], distance = null, zoom = 1 }) { + updateCameraAndControls({ lookAt = null, direction = [0, 0, 1], distance = null, zoom = 1 }) { /* Calculate the camera parameters based on the bounding box of the scene and the camera direction - The camera to look at the center, and rotate around the center of the atoms + The camera to look at the lookAt, and rotate around the lookAt of the atoms. + Position of the camera is defined by the look_at, direction, and distance attributes. */ // normalize the camera direction direction = new THREE.Vector3(...direction).normalize(); const sceneBoundingBox = this.getSceneBoundingBox(); - // center of the bounding box - if (center === null) { - center = sceneBoundingBox.getCenter(new THREE.Vector3()); + // lookAt of the bounding box + if (lookAt === null) { + lookAt = sceneBoundingBox.getCenter(new THREE.Vector3()); } else { - center = new THREE.Vector3(...center); + lookAt = new THREE.Vector3(...lookAt); } const size = calculateBoundingBox(sceneBoundingBox, direction); @@ -198,25 +202,25 @@ export class BlendJS { this.camera.top = cameraHeight / 2; this.camera.bottom = -cameraHeight / 2; - // Adjust camera position based on the center of the bounding box and the camera direction + // Adjust camera position based on the lookAt of the bounding box and the camera direction if (distance === null) { distance = size.z + padding; } - let cameraPosition = center.clone().add(direction.multiplyScalar(distance)); - this.camera.position.copy(cameraPosition); + let cameraPosition = lookAt.clone().add(direction.multiplyScalar(distance)); + this.camera.updatePosition(cameraPosition.x, cameraPosition.y, cameraPosition.z); - this.camera.lookAt(center); + this.camera.lookAt(lookAt); this.camera.updateProjectionMatrix(); - this.camera.zoom = zoom; - // Set the camera target to the center of the atoms - this.controls.target.set(center.x, center.y, center.z); + this.camera.updateZoom(zoom); + // Set the camera target to the lookAt of the atoms + this.controls.target.set(lookAt.x, lookAt.y, lookAt.z); } getSceneBoundingBox() { // Create a bounding box that will include all objects let sceneBoundingBox = new THREE.Box3(); // For each object in the scene, expand the bounding box to include it - this.sceneManager.scene.traverse(function (object) { + this.scene.traverse(function (object) { if (object.isMesh || object.isLineSegments || object.isInstancedMesh) { let objectBoundingBox; // if it is a instancedMesh @@ -251,7 +255,7 @@ export class BlendJS { // loop through renderers to render the scene Object.values(this.renderers).forEach((rndr) => { - rndr.renderer.render(this.sceneManager, this.camera); + rndr.renderer.render(this.scene, this.camera); }); }; @@ -269,7 +273,7 @@ export class BlendJS { renderer.setPixelRatio(highResPixelRatio); // Render the scene for high-res output - renderer.render(this.sceneManager.scene, this.camera); + renderer.render(this.scene, this.camera); // Get the image data URL var imgData = renderer.domElement.toDataURL("image/png"); diff --git a/src/operation/atoms.js b/src/operation/atoms.js index 8d326af..aaa0e7e 100644 --- a/src/operation/atoms.js +++ b/src/operation/atoms.js @@ -6,16 +6,16 @@ class ReplaceOperation extends BaseOperation { static description = "Replace atoms"; static category = "Edit"; - constructor({ weas, element = "C", indices = null }) { + constructor({ weas, symbol = "C", indices = null }) { super(weas); this.indices = indices ? indices : Array.from(this.weas.avr.selectedAtomsIndices); - this.element = element; + this.symbol = symbol; // .copy() provides a fresh instance for restoration this.initialAtoms = weas.avr.atoms.copy(); } execute() { - this.weas.avr.replaceSelectedAtoms(this.element, this.indices); + this.weas.avr.replaceSelectedAtoms(this.symbol, this.indices); } undo() { @@ -23,12 +23,13 @@ class ReplaceOperation extends BaseOperation { this.weas.avr.atoms = this.initialAtoms.copy(); } - adjust(newElement) { - if (!(newElement in elementAtomicNumbers)) { + adjust(newSymbol) { + // if newSymbol not in elementAtomicNumbers, and newSymbol not in this.weas.avr.atoms.species, ship the adjustment + if (!(newSymbol in elementAtomicNumbers || newSymbol in this.weas.avr.atoms.species)) { return; } - this.element = newElement; - this.execute(); // Re-execute with the new element + this.symbol = newSymbol; + this.execute(); // Re-execute with the new symbol } setupGUI(guiFolder) { @@ -36,10 +37,10 @@ class ReplaceOperation extends BaseOperation { renameFolder(guiFolder, "Replace"); guiFolder - .add(this, "element", "H") - .name("Element") + .add(this, "symbol", "H") + .name("Symbol") .onChange((value) => { - this.adjust(this.element); + this.adjust(this.symbol); }); } } @@ -48,16 +49,16 @@ class AddAtomOperation extends BaseOperation { static description = "Add atom"; static category = "Edit"; - constructor({ weas, element = "C", position = { x: 0, y: 0, z: 0 } }) { + constructor({ weas, symbol = "C", position = { x: 0, y: 0, z: 0 } }) { super(weas); // this.weas.avr.selectedAtomsIndices is a set this.position = position; - this.element = element; + this.symbol = symbol; this.initialAtoms = weas.avr.atoms.copy(); } execute() { - this.weas.avr.addAtom(this.element, this.position); + this.weas.avr.addAtom(this.symbol, this.position); } undo() { @@ -65,15 +66,15 @@ class AddAtomOperation extends BaseOperation { this.weas.avr.atoms = this.initialAtoms.copy(); } - adjust(newElement, newPosition) { - // if newElement in elementAtomicNumbers, ship the adjustment - if (!(newElement in elementAtomicNumbers)) { + adjust(newSymbol, newPosition) { + // if newSymbol not in elementAtomicNumbers, ship the adjustment + if (!(newSymbol in elementAtomicNumbers || newSymbol in this.weas.avr.atoms.species)) { return; } this.weas.avr.atoms = this.initialAtoms.copy(); - this.element = newElement; + this.symbol = newSymbol; this.position = newPosition; - this.execute(); // Re-execute with the new element + this.execute(); // Re-execute with the new symbol } setupGUI(guiFolder) { @@ -81,8 +82,8 @@ class AddAtomOperation extends BaseOperation { renameFolder(guiFolder, "Add"); guiFolder - .add(this, "element", "C") - .name("Element") + .add(this, "symbol", "C") + .name("Symbol") .onChange((value) => { this.adjust(value, this.position); }); @@ -90,19 +91,19 @@ class AddAtomOperation extends BaseOperation { .add(this.position, "x", -10, 10) .name("X-axis") .onChange((value) => { - this.adjust(this.element, { ...this.position, x: value }); + this.adjust(this.symbol, { ...this.position, x: value }); }); guiFolder .add(this.position, "y", -10, 10) .name("Y-axis") .onChange((value) => { - this.adjust(this.element, { ...this.position, y: value }); + this.adjust(this.symbol, { ...this.position, y: value }); }); guiFolder .add(this.position, "z", -10, 10) .name("Z-axis") .onChange((value) => { - this.adjust(this.element, { ...this.position, z: value }); + this.adjust(this.symbol, { ...this.position, z: value }); }); } } @@ -111,7 +112,7 @@ class ColorByAttribute extends BaseOperation { static description = "Color by attribute"; static category = "Color"; - constructor({ weas, attribute = "Element", color1 = "#ff0000", color2 = "#0000ff" }) { + constructor({ weas, attribute = "Symbol", color1 = "#ff0000", color2 = "#0000ff" }) { super(weas); // weas.meshPrimitive.settings is a array of objects // deep copy it to avoid modifying the original settings diff --git a/src/operation/mesh.js b/src/operation/mesh.js index 6fc145b..69db225 100644 --- a/src/operation/mesh.js +++ b/src/operation/mesh.js @@ -12,9 +12,7 @@ class BaseMeshOperation extends BaseOperation { execute() { const data = vector3ToArray(this.data); - console.log("data: ", data); this.object = this.drawFunction(data); - console.log("object: ", this.object); this.weas.tjs.scene.add(this.object); } diff --git a/src/operation/object.js b/src/operation/object.js index 1bf2ef9..7b7439c 100644 --- a/src/operation/object.js +++ b/src/operation/object.js @@ -17,13 +17,17 @@ class DeleteOperation extends BaseOperation { } execute() { - this.weas.avr.deleteSelectedAtoms(this.indices); + if (this.indices.length > 0) { + this.weas.avr.deleteSelectedAtoms(this.indices); + } this.weas.objectManager.deleteSelectedObjects(); } undo() { console.log("undo delete"); - this.weas.avr.atoms = this.initialAtoms.copy(); + if (this.indices.length > 0) { + this.weas.avr.atoms = this.initialAtoms.copy(); + } // Restore the deleted objects const selectedObjects = []; this.initialObjectsState.forEach(({ object, parent }) => { @@ -32,7 +36,7 @@ class DeleteOperation extends BaseOperation { parent.add(object); } else { // If no parent was recorded, add it directly to the scene - this.weas.tjs.sceneManager.scene.add(object); + this.weas.tjs.scene.add(object); } selectedObjects.push(object); }); @@ -54,16 +58,20 @@ class CopyOperation extends BaseOperation { } execute() { - this.weas.avr.copyAtoms(this.indices); + if (this.indices.length > 0) { + this.weas.avr.copyAtoms(this.indices); + } this.newObjects = this.weas.objectManager.copySelectedObjects(); } undo() { console.log("Undo copy operation."); - this.weas.avr.atoms = this.initialAtoms.copy(); + if (this.indices.length > 0) { + this.weas.avr.atoms = this.initialAtoms.copy(); + } // Remove the new objects this.newObjects.forEach((object) => { - clearObject(this.weas.tjs.sceneManager.scene, object); + clearObject(this.weas.tjs.scene, object); }); } diff --git a/src/operation/transform.js b/src/operation/transform.js index 2af5bee..972f16a 100644 --- a/src/operation/transform.js +++ b/src/operation/transform.js @@ -5,40 +5,42 @@ class TranslateOperation extends BaseOperation { static description = "Translate"; static category = "Edit"; - constructor(weas, translateVector = new THREE.Vector3()) { + constructor({ weas, vector = new THREE.Vector3() }) { super(weas); // currentFrame - this.currentFrame = weas.currentFrame; + this.currentFrame = weas.avr.currentFrame; // store the selected atoms and the translate vector this.selectedAtomsIndices = Array.from(weas.avr.selectedAtomsIndices); - this.selectedObjects = weas.selectedObjects; - // if translateVector is a normal array [x, y, z], convert it to a THREE.Vector3 - if (Array.isArray(translateVector)) { - translateVector = new THREE.Vector3(translateVector[0], translateVector[1], translateVector[2]); + this.selectedObjects = weas.selectionManager.selectedObjects; + // if vector is a normal array [x, y, z], convert it to a THREE.Vector3 + if (Array.isArray(vector)) { + vector = new THREE.Vector3(vector[0], vector[1], vector[2]); } - this.translateVector = translateVector.clone(); - this.translateVectorGui = { x: translateVector.x, y: translateVector.y, z: translateVector.z }; + this.vector = vector.clone(); + this.vectorGui = { x: vector.x, y: vector.y, z: vector.z }; } execute() { console.log("execute translate"); - this.weas.currentFrame = this.currentFrame; - this.weas.avr.translateSelectedAtoms(this.translateVector, this.selectedAtomsIndices); - this.weas.objectManager.translateSelectedObjects(this.translateVector); + this.weas.avr.currentFrame = this.currentFrame; + this.weas.selectionManager.selectedObjects = this.selectedObjects; + this.weas.avr.translateSelectedAtoms(this.vector, this.selectedAtomsIndices); + this.weas.objectManager.translateSelectedObjects(this.vector); } undo() { console.log("undo translate"); - this.weas.currentFrame = this.currentFrame; - // negative translateVector - const negativeTranslateVector = this.translateVector.clone().negate(); - this.weas.avr.translateSelectedAtoms(negativeTranslateVector, this.selectedAtomsIndices); - this.weas.objectManager.translateSelectedObjects(negativeTranslateVector); + this.weas.avr.currentFrame = this.currentFrame; + // negative vector + const negativevector = this.vector.clone().negate(); + this.weas.avr.translateSelectedAtoms(negativevector, this.selectedAtomsIndices); + this.weas.selectionManager.selectedObjects = this.selectedObjects; + this.weas.objectManager.translateSelectedObjects(negativevector); } adjust() { this.undo(); - this.translateVector = new THREE.Vector3(this.translateVectorGui.x, this.translateVectorGui.y, this.translateVectorGui.z); + this.vector = new THREE.Vector3(this.vectorGui.x, this.vectorGui.y, this.vectorGui.z); this.execute(); // Re-execute with the new translate vector } @@ -47,19 +49,19 @@ class TranslateOperation extends BaseOperation { renameFolder(guiFolder, "Translate"); guiFolder - .add(this.translateVectorGui, "x", -10, 10) + .add(this.vectorGui, "x", -10, 10) .name("X-axis") .onChange((value) => { this.adjust(); }); guiFolder - .add(this.translateVectorGui, "y", -10, 10) + .add(this.vectorGui, "y", -10, 10) .name("Y-axis") .onChange((value) => { this.adjust(); }); guiFolder - .add(this.translateVectorGui, "z", -10, 10) + .add(this.vectorGui, "z", -10, 10) .name("Z-axis") .onChange((value) => { this.adjust(); @@ -71,11 +73,14 @@ class RotateOperation extends BaseOperation { static description = "Rotate"; static category = "Edit"; - constructor(weas, axis, angle) { + constructor({ weas, axis, angle }) { super(weas); - this.currentFrame = weas.currentFrame; + this.currentFrame = weas.avr.currentFrame; this.selectedAtomsIndices = Array.from(weas.avr.selectedAtomsIndices); - this.selectedObjects = weas.selectedObjects; + this.selectedObjects = weas.selectionManager.selectedObjects; + if (Array.isArray(axis)) { + axis = new THREE.Vector3(axis[0], axis[1], axis[2]); + } this.axis = axis; this.angle = angle; this.axisGui = { x: axis.x, y: axis.y, z: axis.z }; @@ -84,7 +89,8 @@ class RotateOperation extends BaseOperation { execute() { // Implementation for rotating selected atoms - this.weas.currentFrame = this.currentFrame; + this.weas.avr.currentFrame = this.currentFrame; + this.weas.selectionManager.selectedObjects = this.selectedObjects; this.weas.avr.rotateSelectedAtoms(this.axis, this.angle, this.selectedAtomsIndices); this.weas.objectManager.rotateSelectedObjects(this.axis, this.angle); } @@ -92,7 +98,8 @@ class RotateOperation extends BaseOperation { undo() { // Undo logic console.log("undo rotate"); - this.weas.currentFrame = this.currentFrame; + this.weas.avr.currentFrame = this.currentFrame; + this.weas.selectionManager.selectedObjects = this.selectedObjects; // rotate the atoms back this.weas.avr.rotateSelectedAtoms(this.axis, -this.angle, this.selectedAtomsIndices); // rotate the objects back @@ -142,27 +149,32 @@ class ScaleOperation extends BaseOperation { static description = "Scale"; static category = "Edit"; - constructor(weas, scale = new THREE.Vector3()) { + constructor({ weas, scale = new THREE.Vector3() }) { super(weas); // currentFrame - this.currentFrame = weas.currentFrame; + this.currentFrame = weas.avr.currentFrame; // store the selected atoms and the scale vector this.selectedAtomsIndices = Array.from(weas.avr.selectedAtomsIndices); - this.selectedObjects = weas.selectedObjects; + this.selectedObjects = weas.selectionManager.selectedObjects; + if (Array.isArray(scale)) { + scale = new THREE.Vector3(scale[0], scale[1], scale[2]); + } this.scale = scale.clone(); this.scaleGui = { x: scale.x, y: scale.y, z: scale.z }; } execute() { console.log("execute scale"); - this.weas.currentFrame = this.currentFrame; + this.weas.avr.currentFrame = this.currentFrame; + this.weas.selectionManager.selectedObjects = this.selectedObjects; // this.weas.avr.scaleSelectedAtoms(this.scale, this.selectedAtomsIndices); this.weas.objectManager.scaleSelectedObjects(this.scale); } undo() { console.log("undo scale"); - this.weas.currentFrame = this.currentFrame; + this.weas.avr.currentFrame = this.currentFrame; + this.weas.selectionManager.selectedObjects = this.selectedObjects; // scale back, by 1/scale const scale = new THREE.Vector3(1 / this.scale.x, 1 / this.scale.y, 1 / this.scale.z); // this.weas.avr.scaleSelectedAtoms(scale, this.selectedAtomsIndices); diff --git a/src/tools/camera.js b/src/tools/camera.js index 8084245..d805b45 100644 --- a/src/tools/camera.js +++ b/src/tools/camera.js @@ -2,17 +2,31 @@ export function setupCameraGUI(gui, camera) { // Create a folder for camera parameters const cameraFolder = gui.addFolder("Camera"); - cameraFolder.add(camera.position, "x", -100, 100).name("X Position"); - cameraFolder.add(camera.position, "y", -100, 100).name("Y Position"); - cameraFolder.add(camera.position, "z", -100, 100).name("Z Position"); + // Temp storage for position to use in onChange callbacks + const position = { x: camera.position.x, y: camera.position.y, z: camera.position.z }; - cameraFolder.add(camera.rotation, "x", -Math.PI, Math.PI).name("X Rotation"); - cameraFolder.add(camera.rotation, "y", -Math.PI, Math.PI).name("Y Rotation"); - cameraFolder.add(camera.rotation, "z", -Math.PI, Math.PI).name("Z Rotation"); - - const cameraParams = { - fov: camera.fov, - near: camera.near, - far: camera.far, - }; + cameraFolder + .add(position, "x", -100, 100) + .name("X Position") + .onChange((newValue) => { + camera.updatePosition(newValue, position.y, position.z); + // Update the temp storage to ensure consistency + position.x = newValue; + }); + cameraFolder + .add(position, "y", -100, 100) + .name("Y Position") + .onChange((newValue) => { + camera.updatePosition(position.x, newValue, position.z); + // Update the temp storage to ensure consistency + position.y = newValue; + }); + cameraFolder + .add(position, "z", -100, 100) + .name("Z Position") + .onChange((newValue) => { + camera.updatePosition(position.x, position.y, newValue); + // Update the temp storage to ensure consistency + position.z = newValue; + }); } diff --git a/src/tools/viewpoint.js b/src/tools/viewpoint.js index 3954315..720df5b 100644 --- a/src/tools/viewpoint.js +++ b/src/tools/viewpoint.js @@ -1,27 +1,27 @@ -export function createViewpointButtons(viewer, gui) { +export function createViewpointButtons(weas, gui) { // Create a folder for the viewpoint buttons - console.log("viewer", viewer); + console.log("weas", weas); const viewpointFolder = gui.addFolder("Viewpoint"); // Create buttons for different viewpoints const viewpoints = { Top: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [0, 0, 100]); + weas.tjs.updateCameraAndControls({ direction: [0, 0, 100] }); }, Bottom: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [0, 0, -100]); + weas.tjs.updateCameraAndControls({ direction: [0, 0, -100] }); }, Left: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [-100, 0, 0]); + weas.tjs.updateCameraAndControls({ direction: [-100, 0, 0] }); }, Right: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [100, 0, 0]); + weas.tjs.updateCameraAndControls({ direction: [100, 0, 0] }); }, Front: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [0, -100, 0]); + weas.tjs.updateCameraAndControls({ direction: [0, -100, 0] }); }, Back: () => { - viewer.tjs.updateCameraAndControls(viewer.atoms.getCenterOfGeometry(), [0, 100, 0]); + weas.tjs.updateCameraAndControls({ direction: [0, 100, 0] }); }, }; diff --git a/src/utils.js b/src/utils.js index 3050dfb..83d6adc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -48,9 +48,7 @@ export function clearObject(scene, obj) { } } -export function getWorldPositionFromScreen(screenX, screenY, camera, plane) { - const ndc = new THREE.Vector2((screenX / window.innerWidth) * 2 - 1, -(screenY / window.innerHeight) * 2 + 1); - +export function getWorldPositionFromScreen(camera, ndc, plane) { const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(ndc, camera); diff --git a/src/weas.js b/src/weas.js index 3c2bdf9..630b867 100644 --- a/src/weas.js +++ b/src/weas.js @@ -47,7 +47,7 @@ class WEAS { this.avr.animate(); Object.values(this.tjs.renderers).forEach((rndr) => { - rndr.renderer.render(this.tjs.sceneManager.scene, this.tjs.camera); + rndr.renderer.render(this.tjs.scene, this.tjs.camera); }); }; @@ -55,7 +55,7 @@ class WEAS { } clear() { - this.tjs.sceneManager.clear(); + this.tjs.scene.clear(); } } diff --git a/tests/e2e/gui.spec.js b/tests/e2e/gui.spec.js index a9227d1..b28ee53 100644 --- a/tests/e2e/gui.spec.js +++ b/tests/e2e/gui.spec.js @@ -210,8 +210,72 @@ test.describe("Selection", () => { // simulate keydown event await page.keyboard.press("g"); // mouse move to the center of the canvas element - await page.mouse.move(page.centerX - 200, page.centerY); - await page.mouse.click(page.centerX - 200, page.centerY); + await page.mouse.move(page.centerX - 100, page.centerY); + await page.mouse.click(page.centerX - 100, page.centerY); await expect(page).toHaveScreenshot(); }); }); + +test.describe("Animation", () => { + test.beforeEach(async ({ page }) => { + await page.goto("http://127.0.0.1:8080/tests/e2e/testAnimation.html"); + + // focus the element + const element = await page.$("#viewer"); + await element.focus(); + const boundingBox = await element.boundingBox(); + // Calculate the center of the element + const centerX = boundingBox.x + boundingBox.width / 2; + const centerY = boundingBox.y + boundingBox.height / 2; + page.centerX = centerX; + page.centerY = centerY; + // Move the mouse to the center of the element + await page.mouse.move(centerX, centerY); + }); + + test("Undo Redos", async ({ page }) => { + await expect(page).toHaveScreenshot("Animation-frame-0.png"); + // simulate keydown event + await page.keyboard.press("g"); + // mouse move to the center of the canvas element + await page.mouse.move(page.centerX + 100, page.centerY); + await page.mouse.click(page.centerX + 100, page.centerY); + // current frame is 0, add name toHaveScreenshot + await expect(page).toHaveScreenshot("Animation-frame-0-move.png"); + // set frame 10 + await page.evaluate(() => { + const timeline = document.getElementById("timeline"); + timeline.value = 10; + // Creating and dispatching the event must happen within the page context + const event = new Event("input", { + bubbles: true, + cancelable: true, + }); + timeline.dispatchEvent(event); + }); + await expect(page).toHaveScreenshot("Animation-frame-10.png"); + //move atoms + await page.keyboard.press("g"); + // mouse move to the center of the canvas element + await page.mouse.move(page.centerX + 200, page.centerY); + await page.mouse.click(page.centerX + 200, page.centerY); + await expect(page).toHaveScreenshot("Animation-frame-10-move.png"); + // undo + await page.keyboard.down("Control"); + await page.keyboard.press("z"); + await expect(page).toHaveScreenshot("Animation-frame-10-undo.png"); + // undo, should go back to frame 0 + const element = await page.$("#undo"); + await element.click(); + await expect(page).toHaveScreenshot("Animation-frame-0-undo.png"); + // redo + await page.keyboard.down("Control"); + await page.keyboard.press("y"); + await expect(page).toHaveScreenshot("Animation-frame-0-redo.png"); + // redo + await page.waitForSelector("#redo", { state: "attached" }); + const element2 = await page.$("#redo"); + await element2.click(); + await expect(page).toHaveScreenshot("Animation-frame-10-redo.png"); + }); +}); diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-chromium-linux.png new file mode 100644 index 0000000..b2db8ac Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-move-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-move-chromium-linux.png new file mode 100644 index 0000000..35987ba Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-move-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-redo-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-redo-chromium-linux.png new file mode 100644 index 0000000..0c95b95 Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-redo-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-undo-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-undo-chromium-linux.png new file mode 100644 index 0000000..34dd648 Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-0-undo-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-chromium-linux.png new file mode 100644 index 0000000..80b069d Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-move-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-move-chromium-linux.png new file mode 100644 index 0000000..3e35e2f Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-move-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-redo-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-redo-chromium-linux.png new file mode 100644 index 0000000..ed819ed Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-redo-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-undo-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-undo-chromium-linux.png new file mode 100644 index 0000000..3127809 Binary files /dev/null and b/tests/e2e/gui.spec.js-snapshots/Animation-frame-10-undo-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Duplicate-Atoms-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Duplicate-Atoms-1-chromium-linux.png index a73f80c..dec4cf2 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Duplicate-Atoms-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Duplicate-Atoms-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Move-Atoms-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Move-Atoms-1-chromium-linux.png index 43aff36..482fe28 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Move-Atoms-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Move-Atoms-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Delete-Object-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Delete-Object-1-chromium-linux.png index bdb2796..aa658db 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Delete-Object-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Delete-Object-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Duplicate-Object-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Duplicate-Object-1-chromium-linux.png index a58c611..de3388f 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Duplicate-Object-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Duplicate-Object-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Mesh-Primitive-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Mesh-Primitive-1-chromium-linux.png index 7aabcb1..808b244 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Mesh-Primitive-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Mesh-Primitive-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Move-Object-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Move-Object-1-chromium-linux.png index 5e588c4..0304837 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Move-Object-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Move-Object-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Rotate-Object-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Rotate-Object-1-chromium-linux.png index a983521..d7c67ba 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Rotate-Object-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Rotate-Object-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Scale-Object-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Scale-Object-1-chromium-linux.png index e5eedaa..6f0b97e 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Object-Scale-Object-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Object-Scale-Object-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-1-chromium-linux.png index 43aff36..482fe28 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-1-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-3-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-3-chromium-linux.png index 48df32a..dcbba0e 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-3-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-3-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-4-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-4-chromium-linux.png index 27df862..9ca9a83 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-4-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-4-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-5-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-5-chromium-linux.png index 5f12ad7..19d3c98 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-5-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-5-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-6-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-6-chromium-linux.png index 80651af..69e3080 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-6-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Edit-Undo-Redos-6-chromium-linux.png differ diff --git a/tests/e2e/gui.spec.js-snapshots/Selection-Move-Selected-1-chromium-linux.png b/tests/e2e/gui.spec.js-snapshots/Selection-Move-Selected-1-chromium-linux.png index da5b7ae..1c793f6 100644 Binary files a/tests/e2e/gui.spec.js-snapshots/Selection-Move-Selected-1-chromium-linux.png and b/tests/e2e/gui.spec.js-snapshots/Selection-Move-Selected-1-chromium-linux.png differ diff --git a/tests/e2e/testAnimation.html b/tests/e2e/testAnimation.html new file mode 100644 index 0000000..09049d8 --- /dev/null +++ b/tests/e2e/testAnimation.html @@ -0,0 +1,35 @@ + + + + + + WEAS Test + + +
+ + + + diff --git a/tests/e2e/testPrimitive.html b/tests/e2e/testPrimitive.html index 2032931..3faae5e 100644 --- a/tests/e2e/testPrimitive.html +++ b/tests/e2e/testPrimitive.html @@ -20,10 +20,11 @@ let domElement = document.getElementById("viewer"); let editor = new weas.WEAS({ domElement }); - editor.ops.mesh.AddCubeOperation({ position: [-5, 0, 0], size: 2 }); - editor.ops.mesh.AddSphereOperation({ position: [5, 2, 0], radius: 2, color: "#00FF00" }); + editor.ops.mesh.AddCubeOperation({ position: [0, 0, 0], scale: [1, 1, 1], color: "#0000FF", opacity: 0.5 }); + editor.ops.mesh.AddSphereOperation({ position: [-5, 0, 0], scale: [1, 1, 1], color: "#00FF00", opacity: 0.5 }); + editor.ops.mesh.AddCylinderOperation({ position: [5, 0, 0], scale: [1, 1, 1], color: "#bd0d87", opacity: 0.5 }); editor.ops.hideGUI(); - editor.selectionManager.selectedObjects = [editor.tjs.scene.children[5]]; + editor.selectionManager.selectedObjects = [editor.tjs.scene.children[3]]; editor.render();