Skip to content

Commit ac50cb9

Browse files
committed
update: refactor orbit controls implementation
1 parent f930bdd commit ac50cb9

File tree

7 files changed

+84
-60
lines changed

7 files changed

+84
-60
lines changed

dist/mixins/image.d.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
export function ImageMixin(superclass: any): {
2-
new (config: any): {
2+
new (): {
33
[x: string]: any;
4-
drawUnitCell: any;
4+
takeScreenshot(): void;
5+
getScreenshotImage(): any;
6+
createRotatingGifData(options?: {}): Promise<any>;
7+
takeGifScreenshot(options?: {}): Promise<void>;
58
};
69
[x: string]: any;
710
};

dist/mixins/image.js

+52-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,58 @@
1+
import { saveImageDataToFile } from "@exabyte-io/cove.js/dist/utils/downloader";
2+
import { createGIFAsync } from "./utils";
13
/*
24
* Mixin containing the logic for dealing with the images, GIFs.
35
*/
46
export const ImageMixin = (superclass) => class extends superclass {
5-
constructor(config) {
6-
super(config);
7-
this.drawUnitCell = this.drawUnitCell.bind(this);
7+
takeScreenshot() {
8+
saveImageDataToFile(this.getScreenshotImage());
9+
}
10+
getScreenshotImage() {
11+
const canvas = this.renderer.domElement;
12+
canvas.getContext("2d", { willReadFrequently: true });
13+
return canvas.toDataURL("image/png");
14+
}
15+
async createRotatingGifData(options = {}) {
16+
const sampleInterval = options.sampleInterval || 20; // parts of image in pixels
17+
const totalGifDuration = options.totalDuration || 3; // seconds
18+
const animationDuration = options.animationDuration || 1; // seconds
19+
const FPS = 60; // frames per second
20+
const animationFrames = FPS * animationDuration;
21+
const totalGifFrames = animationFrames;
22+
const autoRotateSpeed = FPS / animationDuration; // RPM
23+
const frameDuration = totalGifDuration / totalGifFrames;
24+
// const animationStep = animationDuration / totalFrames;
25+
const canvas = this.renderer.domElement;
26+
canvas.willReadFrequently = true;
27+
const { width, height } = canvas;
28+
if (this.orbitControls.autoRotate) {
29+
alert("Please disable auto-rotation before creating a GIF.");
30+
return;
31+
}
32+
// Store original auto-rotate settings
33+
const originalSpeed = this.orbitControls.autoRotateSpeed;
34+
this.orbitControls.autoRotateSpeed = autoRotateSpeed;
35+
this.orbitControls.autoRotate = true;
36+
const frames = [];
37+
for (let i = 0; i < animationFrames; i += 1) {
38+
this.performOrbitControlsAnimation(() => frames.push(this.getScreenshotImage()));
39+
}
40+
const gifData = await createGIFAsync({
41+
// ...options,
42+
images: frames,
43+
gifWidth: width,
44+
gifHeight: height,
45+
sampleInterval,
46+
frameDuration,
47+
});
48+
this.orbitControls.rotateSpeed = originalSpeed;
49+
this.orbitControls.autoRotate = false;
50+
canvas.willReadFrequently = false;
51+
return gifData;
52+
}
53+
async takeGifScreenshot(options = {}) {
54+
const gifDataUrl = await this.createRotatingGifData(options);
55+
const fileName = this._structure.name || this._structure.formula || "wave-visualization" + ".gif";
56+
saveImageDataToFile(gifDataUrl, fileName);
857
}
958
};

dist/mixins/utils.d.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
export function createRotatingGifData(wave: any, options?: {}): Promise<any>;
1+
export function createGIFAsync({ images, gifWidth, gifHeight, numFrames, frameDuration, sampleInterval, }: {
2+
images: any;
3+
gifWidth: any;
4+
gifHeight: any;
5+
numFrames: any;
6+
frameDuration: any;
7+
sampleInterval: any;
8+
}): Promise<any>;
29
export function UtilsMixin(superclass: any): {
310
new (): {
411
[x: string]: any;

dist/mixins/utils.js

+2-36
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ export const ApplyGlow = (meshObjet, baseColor, offset = 0) => {
5959
meshObjet.material.emissive.setHSL(hue, saturation, atomHSL.l);
6060
}
6161
};
62-
function createGIFAsync(options) {
62+
export function createGIFAsync({ images, gifWidth, gifHeight, numFrames, frameDuration, sampleInterval, }) {
6363
return new Promise((resolve, reject) => {
64-
gifshot.createGIF(options, (obj) => {
64+
gifshot.createGIF({ images, gifWidth, gifHeight, numFrames, frameDuration, sampleInterval }, (obj) => {
6565
if (!obj.error) {
6666
resolve(obj.image); // Resolve with the GIF data URL
6767
}
@@ -71,37 +71,3 @@ function createGIFAsync(options) {
7171
});
7272
});
7373
}
74-
export async function createRotatingGifData(wave, options = {}) {
75-
const ROTATION_SPEED = options.rotationSpeed || 60; // RPM
76-
const frameDuration = options.frameDuration || 0.05; // seconds
77-
const sampleInterval = ROTATION_SPEED * frameDuration;
78-
const frames = [];
79-
const totalFrames = ROTATION_SPEED;
80-
const canvas = wave.renderer.domElement;
81-
canvas.willReadFrequently = true;
82-
const { width } = canvas;
83-
const { height } = canvas;
84-
if (wave.orbitControls.autoRotate) {
85-
alert("Please disable auto-rotation before creating a GIF.");
86-
return;
87-
}
88-
// Store original auto-rotate settings
89-
const originalSpeed = wave.orbitControls.autoRotateSpeed;
90-
wave.orbitControls.autoRotateSpeed = ROTATION_SPEED;
91-
wave.orbitControls.autoRotate = true;
92-
for (let i = 0; i < totalFrames; i += 1) {
93-
wave.performOrbitControlsAnimation(() => frames.push(wave.getScreenshotImage()));
94-
}
95-
const gifData = await createGIFAsync({
96-
images: frames,
97-
gifWidth: width,
98-
gifHeight: height,
99-
numFrames: totalFrames,
100-
frameDuration,
101-
sampleInterval,
102-
});
103-
wave.orbitControls.rotateSpeed = originalSpeed;
104-
wave.orbitControls.autoRotate = false;
105-
canvas.willReadFrequently = false;
106-
return gifData;
107-
}

dist/wave.d.ts

-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ export class Wave {
1010
rebuildScene(): void;
1111
render(): void;
1212
doFunc(func: any): void;
13-
takeScreenshot(): void;
14-
getScreenshotImage(): any;
1513
clearView(): void;
1614
adjustCamerasAndOrbitControlsToCell(): void;
1715
collectAllAtoms(): any[];
@@ -21,5 +19,4 @@ export class Wave {
2119
*/
2220
refillSelectedAtoms(): void;
2321
selectedAtoms: any[] | undefined;
24-
takeGifScreenshot(options?: {}): Promise<void>;
2522
}

dist/wave.js

+2-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import "./stylesheets/main.css";
33
import { mix } from "mixwith";
44
import * as THREE from "three";
5-
import { saveImageDataToFile } from "@exabyte-io/cove.js/dist/utils/downloader";
65
import { ATOM_GROUP_NAME } from "./enums";
76
import { AtomsMixin } from "./mixins/atoms";
87
import { BondsMixin } from "./mixins/bonds";
@@ -13,8 +12,8 @@ import { ControlsMixin } from "./mixins/controls";
1312
import { LabelsMixin } from "./mixins/labels";
1413
import { MeasurementMixin } from "./mixins/measurement";
1514
import { RepetitionMixin } from "./mixins/repetition";
16-
import { createRotatingGifData } from "./mixins/utils";
1715
import SETTINGS from "./settings";
16+
import { ImageMixin } from "./mixins/image";
1817
// eslint-disable-next-line import/no-cycle
1918
const TV3 = THREE.Vector3;
2019
const TCo = THREE.Color;
@@ -195,7 +194,7 @@ class WaveBase {
195194
/**
196195
* Wave draws atoms as spheres according to the material geometry passed.
197196
*/
198-
export class Wave extends mix(WaveBase).with(AtomsMixin, BondsMixin, CellMixin, RepetitionMixin, ControlsMixin, BoundaryMixin, LabelsMixin, MeasurementMixin) {
197+
export class Wave extends mix(WaveBase).with(AtomsMixin, BondsMixin, CellMixin, RepetitionMixin, ControlsMixin, BoundaryMixin, LabelsMixin, MeasurementMixin, ImageMixin) {
199198
/**
200199
*
201200
* @param {Object} config
@@ -208,12 +207,6 @@ export class Wave extends mix(WaveBase).with(AtomsMixin, BondsMixin, CellMixin,
208207
this.render = this.render.bind(this);
209208
this.doFunc = this.doFunc.bind(this);
210209
}
211-
takeScreenshot() {
212-
saveImageDataToFile(this.getScreenshotImage());
213-
}
214-
getScreenshotImage() {
215-
return this.renderer.domElement.toDataURL("image/png");
216-
}
217210
clearView() {
218211
while (this.structureGroup.children.length) {
219212
this.structureGroup.remove(this.structureGroup.children[0]);
@@ -279,9 +272,4 @@ export class Wave extends mix(WaveBase).with(AtomsMixin, BondsMixin, CellMixin,
279272
doFunc(func) {
280273
func(this);
281274
} // for scripting
282-
async takeGifScreenshot(options = {}) {
283-
const gifDataUrl = await createRotatingGifData(this, options);
284-
const fileName = this._structure.name || this._structure.formula || "wave-visualization" + ".gif";
285-
saveImageDataToFile(gifDataUrl, fileName);
286-
}
287275
}

src/mixins/controls.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const OrbitControlsMixin = (superclass) =>
2525

2626
this.initSecondAxes = this.initSecondAxes.bind(this);
2727
this.updateSecondAxes = this.updateSecondAxes.bind(this);
28+
29+
// Bind methods to context to avoid losing `this` reference in requestAnimationFrame
30+
this.performOrbitControlsAnimation = this.performOrbitControlsAnimation.bind(this);
2831
}
2932

3033
initOrbitControls(enabled = false) {
@@ -75,14 +78,25 @@ const OrbitControlsMixin = (superclass) =>
7578
enableOrbitControlsAnimation() {
7679
if (!this.orbitControls) return;
7780
this.orbitControls.autoRotate = true;
78-
this.animationFrameId = window.requestAnimationFrame(this.enableOrbitControlsAnimation);
81+
this.performOrbitControlsAnimation();
82+
}
83+
84+
// eslint-disable-next-line class-methods-use-this
85+
performOrbitControlsAnimation(action = () => {}) {
86+
this.animationFrameId = window.requestAnimationFrame(
87+
this.performOrbitControlsAnimation,
88+
);
7989
// required if controls.enableDamping or controls.autoRotate are set to true
8090
this.orbitControls.update();
8191
this.render();
92+
if (typeof action === "function") {
93+
action();
94+
}
8295
}
8396

8497
disableOrbitControlsAnimation() {
8598
if (!this.orbitControls) return;
99+
this.orbitControls.autoRotate = false;
86100
window.cancelAnimationFrame(this.animationFrameId);
87101
this.animationFrameId = null;
88102
}

0 commit comments

Comments
 (0)