Skip to content

Commit

Permalink
Pull in custom representations and VFX from NGL fork, fix some mouse …
Browse files Browse the repository at this point in the history
…input bugginess
  • Loading branch information
luxaritas committed Jan 25, 2022
1 parent 243e16f commit 905c8e7
Show file tree
Hide file tree
Showing 17 changed files with 969 additions and 91 deletions.
23 changes: 12 additions & 11 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@
"lodash.debounce": "^4.0.8",
"loglevel": "^1.7.1",
"marked": "^2.0.7",
"ngl": "https://github.com/eternagame/ngl/archive/aedf4afa935ce60fd4dbcf4c4100d4381a69c9e0.tar.gz",
"ngl": "https://github.com/eternagame/ngl/archive/dc6f494f4231dd2626b036094fc2535185915598.tar.gz",
"pchip": "^1.0.2",
"pixi-filters": "^4.1.1",
"pixi-multistyle-text": "https://github.com/eternagame/pixi-multistyle-text/archive/1f2283b1d4a1bb262b57783e1bf2f81802a17809.tar.gz",
"pixi.js": "^6.0.4",
"regenerator-runtime": "^0.13.7",
"store": "^2.0.12",
"three": "^0.118.0",
"upng-js": "^2.1.0",
"uuid": "^8.3.2",
"webfontloader": "^1.6.28"
Expand Down
13 changes: 13 additions & 0 deletions src/eterna/EternaApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ export default class EternaApp extends FlashbangApp {
Eterna.client = new GameClient(Eterna.SERVER_URL);
Eterna.gameDiv = document.getElementById(this._params.containerID);

// Without this, we stop the pointer events from propagating to NGL in Pose3D/PointerEventPropagator,
// but the original mouse events will still get fired, so NGL will get confused since it tracks some
// events that happen outside its canvas, and these will be targeted to the Pixi canvas.
if (window.PointerEvent) {
this.view.addEventListener('mousedown', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mouseenter', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mouseleave', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mousemove', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mouseout', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mouseover', (e) => e.stopImmediatePropagation());
this.view.addEventListener('mouseup', (e) => e.stopImmediatePropagation());
}

Assert.assertIsDefined(this._regs);

this._regs.add(Eterna.settings.soundMute.connectNotify((mute) => {
Expand Down
6 changes: 3 additions & 3 deletions src/eterna/mode/GameMode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,16 +292,16 @@ export default abstract class GameMode extends AppMode {
this.addObject(this._pose3D, this.dialogLayer);
this.regs?.add(this._pose3D.baseHovered.connect((closestIndex) => {
this._poses.forEach((pose) => {
pose.on3DPickingMouseMoved(closestIndex - 1);
pose.on3DPickingMouseMoved(closestIndex);
});
}));
this.regs?.add(this._pose3D.baseClicked.connect((closestIndex) => {
this._poses.forEach((pose) => {
pose.simulateMousedownCallback(closestIndex - 1);
pose.simulateMousedownCallback(closestIndex);
});
}));
this.regs?.add(this._poses[0].baseHovered.connect(
(val: {index: number; color: number}) => this._pose3D?.hover3D(val.index, val.color)
(val: number) => this._pose3D?.hover3D(val)
));
this.regs?.add(this._poses[0].baseMarked.connect(
(val: number) => this._pose3D?.mark3D(val)
Expand Down
6 changes: 3 additions & 3 deletions src/eterna/pose2D/Pose2D.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class Pose2D extends ContainerObject implements Updatable {
public static readonly ZOOM_SPACINGS: number[] = [45, 30, 20, 14, 7];

public readonly baseMarked = new Signal<number>();
public readonly baseHovered = new Signal<{index: number; color: number;}>();
public readonly baseHovered = new Signal<number>();
public readonly basesSparked = new Signal<number[]>();

constructor(poseField: PoseField, editable: boolean, annotationManager: AnnotationManager) {
Expand Down Expand Up @@ -1236,7 +1236,7 @@ export default class Pose2D extends ContainerObject implements Updatable {
}

if (closestIndex >= 0 && this._currentColor >= 0) {
this.baseHovered.emit({index: closestIndex + 1, color: this._currentColor});
this.baseHovered.emit(closestIndex);

this.onBaseMouseMove(closestIndex);

Expand All @@ -1260,7 +1260,7 @@ export default class Pose2D extends ContainerObject implements Updatable {
}
} else {
this._lastColoredIndex = -1;
this.baseHovered.emit({index: -1, color: 0});
this.baseHovered.emit(-1);
}

if (!this._coloring) {
Expand Down
193 changes: 193 additions & 0 deletions src/eterna/pose3D/BaseHighlightGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {Stage} from 'ngl';
import {
BufferGeometry, Group, Mesh, MeshBasicMaterial, Vector3
} from 'three';
import {OutlinePass} from 'three/examples/jsm/postprocessing/OutlinePass';
import EternaEllipsoidBuffer from './EternaEllipsoidBuffer';

type HighlightMesh = Mesh & {material: MeshBasicMaterial};

export default class BaseHighlightGroup extends Group {
constructor(stage: Stage, selectedOutlinePass: OutlinePass) {
super();
this._stage = stage;
this._selectedOutlinePass = selectedOutlinePass;
}

public clearHover() {
if (this._hoverHighlight) {
this.remove(this._hoverHighlight.mesh);
const baseIndex = this._hoverHighlight.baseIndex;
this._hoverHighlight = null;
// Since we're no longer highlighted, shrink the changed highlight to be just around
// the base rather than the highlight
if (this._changeHighlights.has(baseIndex)) {
this.addChanged(baseIndex, false);
}
}
}

public switchHover(baseIndex: number, color: number) {
this.clearHover();

const newMesh = this.highlight(baseIndex, color, 0.6, 1.3);
if (newMesh) {
this._hoverHighlight = {baseIndex, mesh: newMesh};
// Since we're now highlighted, expand the changed highlight to be around the entire
// hover highlight rather than just the base (as otherwise it would look weird since the
// change highlight would be partially masked by the hover highlight)
if (this._changeHighlights.has(baseIndex)) this.addChanged(baseIndex, false);
}
}

public updateHoverColor(getColor: (baseIndex: number) => number) {
if (this._hoverHighlight) {
this.switchHover(this._hoverHighlight.baseIndex, getColor(this._hoverHighlight.baseIndex));
}
}

public addChanged(baseIndex: number, resetExpire: boolean = true) {
if (resetExpire) {
const oldHighlight = this._changeHighlights.get(baseIndex);
if (oldHighlight) {
clearTimeout(oldHighlight.expireHandle);
oldHighlight.expireHandle = setTimeout(
this.clearChangedHighlight.bind(this) as TimerHandler, 3000, baseIndex
);
} else {
const hovered = this._hoverHighlight?.baseIndex === baseIndex;
const newMesh = this.highlight(baseIndex, 0xFFFFFF, 0, hovered ? 1.3 : 1);
if (newMesh) {
const expireHandle = setTimeout(
this.clearChangedHighlight.bind(this) as TimerHandler, 3000, baseIndex
);
this._changeHighlights.set(baseIndex, {mesh: newMesh, expireHandle});
}
}
} else {
// Just re-generate the mesh without affecting the highlight expiration
const highlight = this._changeHighlights.get(baseIndex);
if (!highlight) return;

this.remove(highlight.mesh);

const hovered = this._hoverHighlight?.baseIndex === baseIndex;
const newMesh = this.highlight(baseIndex, 0xFFFFFF, 0, hovered ? 1.3 : 1);
if (newMesh) highlight.mesh = newMesh;
}

// While technically wasteful to do this in all cases even if highlights haven't changed, we'll
// do this here for robustness just to make sure we don't miss any edge cases
this._selectedOutlinePass.selectedObjects = Array
.from(this._changeHighlights.values())
.map((highlight) => highlight.mesh);
}

private clearChangedHighlight(baseIndex: number) {
const oldHighlight = this._changeHighlights.get(baseIndex);
if (oldHighlight) {
this.remove(oldHighlight.mesh);
this._changeHighlights.delete(baseIndex);
}

this._selectedOutlinePass.selectedObjects = Array
.from(this._changeHighlights.values())
.map((highlight) => highlight.mesh);

this._stage.viewer.requestRender();
}

public toggleMark(baseIndex: number) {
const oldMesh = this._markHighlights.get(baseIndex);
if (oldMesh) {
this.remove(oldMesh);
this._markHighlights.delete(baseIndex);
} else {
const newMesh = this.highlight(baseIndex, 0x000000, 0.6, 1);
if (newMesh) this._markHighlights.set(baseIndex, newMesh);
}
this.updateFlashing();
}

private updateFlashing() {
const shouldBeFlashing = this._markHighlights.size > 0;
if (shouldBeFlashing && this._flashHandle === null) {
this._flashHandle = setInterval(this.flash.bind(this) as TimerHandler, 500);
} else if (!shouldBeFlashing && this._flashHandle !== null) {
clearInterval(this._flashHandle);
this._flashHandle = null;
}
}

private flash() {
this._flashCount++;

for (const mesh of this._markHighlights.values()) {
mesh.material.opacity = (this._flashCount % 2) * 0.6;
}

this._stage.viewer.requestRender();
}

private highlight(baseIndex: number, color: number, opacity: number, scale: number): HighlightMesh | null {
const rep = this._stage.getRepresentationsByName('eterna').first;
if (!rep) return null;

const baseBuff = rep.repr.bufferList.find(
(buff): buff is EternaEllipsoidBuffer => buff instanceof EternaEllipsoidBuffer
);
if (!baseBuff) return null;

const basePositions: Vector3[] = [];
const positions = baseBuff.geometry.getAttribute('position').array;
const ids = baseBuff.geometry.getAttribute('primitiveId').array;
for (let i = 0; i < ids.length; i++) {
if (ids[i] === baseIndex) {
basePositions.push(new Vector3(
positions[i * 3],
positions[i * 3 + 1],
positions[i * 3 + 2]
));
}
}

const selGeometry = new BufferGeometry();
selGeometry.setFromPoints(basePositions);

if (scale !== 1) {
const avgPos = new Vector3();
for (const pos of basePositions) {
avgPos.add(pos);
}
avgPos.divideScalar(basePositions.length);

selGeometry.translate(-avgPos.x, -avgPos.y, -avgPos.z);
selGeometry.scale(scale, scale, scale);
selGeometry.translate(avgPos.x, avgPos.y, avgPos.z);
}

const mat = new MeshBasicMaterial({
color,
opacity,
transparent: true,
depthWrite: false
});
const mesh = new Mesh(selGeometry, mat);
mesh.name = baseIndex.toString();
this.add(mesh);

this._stage.viewer.requestRender();

return mesh;
}

private _stage: Stage;
private _selectedOutlinePass: OutlinePass;

private _hoverHighlight: {baseIndex: number, mesh: HighlightMesh} | null = null;
private _changeHighlights = new Map<number, {mesh: HighlightMesh; expireHandle: number}>();
private _markHighlights = new Map<number, HighlightMesh>();

private _flashHandle: number | null = null;
private _flashCount = 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export function getBaseColor(base: RNABase) {
}

export default function createColorScheme(sequence: Value<Sequence>) {
class NGLColorScheme extends Colormaker {
class EternaColorScheme extends Colormaker {
public atomColor(atom: AtomProxy): number {
return getBaseColor(sequence.value.nt(atom.resno - 1));
return getBaseColor(sequence.value.nt(atom.residueIndex));
}
}

return ColormakerRegistry._addUserScheme(NGLColorScheme);
return ColormakerRegistry._addUserScheme(EternaColorScheme);
}
28 changes: 28 additions & 0 deletions src/eterna/pose3D/EternaEllipsoidBuffer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {EllipsoidBuffer} from 'ngl';
import type {EllipsoidBufferData} from 'ngl/dist/declarations/buffer/ellipsoid-buffer';

type EternaEllipsoidBufferParameters = EternaEllipsoidBuffer['defaultParameters'];

export default class EternaEllipsoidBuffer extends EllipsoidBuffer {
public get defaultParameters() {
return {
...super.defaultParameters,
vScale: 1
};
}

constructor(data: EllipsoidBufferData, params: Partial<EternaEllipsoidBufferParameters> = {}) {
super(data, params);
this._vScale = params.vScale ?? 1;
// Reset the attributes now that we have our proper vScale set
this.setAttributes(data, true);
}

public setAttributes(data: Partial<EllipsoidBufferData> = {}, initNormals?: boolean) {
// Scale sphere in 3 axises to make ellipsoid
const radius = data.radius ? data.radius.map((r) => r * this._vScale) : undefined;
super.setAttributes({...data, radius}, initNormals);
}

private _vScale: number = 1;
}
Loading

0 comments on commit 905c8e7

Please sign in to comment.