mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 06:34:23 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fac8a8f77 | ||
|
|
7266c67e32 | ||
|
|
50c8d09742 | ||
|
|
7377947975 | ||
|
|
a3c4daf30a | ||
|
|
9d7e6f1d99 | ||
|
|
9e105020e3 | ||
|
|
93df548cfe | ||
|
|
a0b1593c82 | ||
|
|
fc81e08d73 | ||
|
|
5369fa5adf | ||
|
|
316a77c716 | ||
|
|
42dfa69ad7 | ||
|
|
cae4eb8b0e | ||
|
|
5514b24fdf | ||
|
|
d570bc352e |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file, following t
|
||||
|
||||
Note that since we don't clearly distinguish between a public and private interfaces there will be changes in non-major versions that are potentially breaking. If we make breaking changes to less used interfaces we will highlight it in here.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- [empty]
|
||||
|
||||
## [v2.0.5] - 2021-04-26
|
||||
|
||||
- Ability to pass ``Canvas3DContext`` to ``PluginContext.fromCanvas``.
|
||||
- Relative frame support for ``Canvas3D`` viewport.
|
||||
- Fix bug in screenshot copy UI.
|
||||
- Add ability to select residues from a list of identifiers to the Selection UI.
|
||||
- Fix SSAO bugs when used with ``Canvas3D`` viewport.
|
||||
- Support for full pausing (no draw) rendering: ``Canvas3D.pause(true)``.
|
||||
- Add `MeshBuilder.addMesh`.
|
||||
- Add `Torus` primitive.
|
||||
- Lazy volume loading support.
|
||||
- [Breaking] ``Viewer.loadVolumeFromUrl`` signature change.
|
||||
- ``loadVolumeFromUrl(url, format, isBinary, isovalues, entryId)`` => ``loadVolumeFromUrl({ url, format, isBinary }, isovalues, { entryId, isLazy })``
|
||||
- Add ``TextureMesh`` support to ``geo-export`` extension.
|
||||
|
||||
## [v2.0.4] - 2021-04-20
|
||||
|
||||
- [WIP] Mesh export extension
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^1.0.38",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "2.0.4",
|
||||
"version": "2.0.5",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
var viewer = new molstar.Viewer('app', {
|
||||
layoutIsExpanded: false,
|
||||
layoutIsExpanded: true,
|
||||
layoutShowControls: false,
|
||||
layoutShowRemoteState: false,
|
||||
layoutShowSequence: true,
|
||||
@@ -37,6 +37,20 @@
|
||||
});
|
||||
viewer.loadPdb('7bv2');
|
||||
viewer.loadEmdb('EMD-30210', { detail: 6 });
|
||||
|
||||
// viewer.loadVolumeFromUrl({
|
||||
// url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6',
|
||||
// format: 'dscif',
|
||||
// isBinary: true
|
||||
// }, [{
|
||||
// type: 'relative',
|
||||
// value: 1,
|
||||
// color: 0x3377aa
|
||||
// }], {
|
||||
// entryId: 'EMD-30210',
|
||||
// isLazy: true
|
||||
// });
|
||||
|
||||
// viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } })
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -243,17 +243,29 @@ export class Viewer {
|
||||
}));
|
||||
}
|
||||
|
||||
async loadVolumeFromUrl(url: string, format: BuildInVolumeFormat, isBinary: boolean, isovalues: VolumeIsovalueInfo[], entryId?: string) {
|
||||
async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string, isLazy?: boolean }) {
|
||||
const plugin = this.plugin;
|
||||
|
||||
if (!plugin.dataFormats.get(format)) {
|
||||
throw new Error(`Unknown density format: ${format}`);
|
||||
}
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
|
||||
if (options?.isLazy) {
|
||||
const update = this.plugin.build();
|
||||
update.toRoot().apply(StateTransforms.Data.LazyVolume, {
|
||||
url,
|
||||
format,
|
||||
entryId: options?.entryId,
|
||||
isBinary,
|
||||
isovalues: isovalues.map(v => ({ alpha: 1, ...v }))
|
||||
});
|
||||
return update.commit();
|
||||
}
|
||||
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary, label: options?.entryId }, { state: { isGhost: true } });
|
||||
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId });
|
||||
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
@@ -261,7 +273,7 @@ export class Viewer {
|
||||
for (const iso of isovalues) {
|
||||
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
|
||||
@@ -31,7 +31,7 @@ export class GeometryControls extends PluginComponent {
|
||||
const objExporter = new ObjExporter(filename);
|
||||
for (let i = 0, il = renderObjects.length; i < il; ++i) {
|
||||
await ctx.update({ message: `Exporting object ${i}/${il}` });
|
||||
await objExporter.add(renderObjects[i], ctx);
|
||||
await objExporter.add(renderObjects[i], this.plugin.canvas3d?.webgl!, ctx);
|
||||
}
|
||||
const { obj, mtl } = objExporter.getData();
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ import { LinesValues } from '../../mol-gl/renderable/lines';
|
||||
import { PointsValues } from '../../mol-gl/renderable/points';
|
||||
import { SpheresValues } from '../../mol-gl/renderable/spheres';
|
||||
import { CylindersValues } from '../../mol-gl/renderable/cylinders';
|
||||
import { TextureMeshValues } from '../../mol-gl/renderable/texture-mesh';
|
||||
import { BaseValues, SizeValues } from '../../mol-gl/renderable/schema';
|
||||
import { TextureImage } from '../../mol-gl/renderable/util';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { addCylinder } from '../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
@@ -32,7 +34,7 @@ type RenderObjectExportData = {
|
||||
}
|
||||
|
||||
interface RenderObjectExporter<D extends RenderObjectExportData> {
|
||||
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext): Promise<void> | undefined
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext): Promise<void> | undefined
|
||||
getData(): D
|
||||
}
|
||||
|
||||
@@ -80,6 +82,17 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
return size * values.uSizeFactor.ref.value;
|
||||
}
|
||||
|
||||
private static getGroup(groups: Float32Array | Uint8Array, i: number): number {
|
||||
const i4 = i * 4;
|
||||
const r = groups[i4];
|
||||
const g = groups[i4 + 1];
|
||||
const b = groups[i4 + 2];
|
||||
if (groups instanceof Float32Array) {
|
||||
return decodeFloatRGB(r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5);
|
||||
}
|
||||
return decodeFloatRGB(r, g, b);
|
||||
}
|
||||
|
||||
private updateMaterial(color: Color, alpha: number) {
|
||||
if (this.currentColor === color && this.currentAlpha === alpha) return;
|
||||
|
||||
@@ -111,11 +124,12 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
}
|
||||
}
|
||||
|
||||
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array, groups: Float32Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, ctx: RuntimeContext) {
|
||||
private async addMeshWithColors(vertices: Float32Array, normals: Float32Array, indices: Uint32Array | undefined, groups: Float32Array | Uint8Array, vertexCount: number, drawCount: number, values: BaseValues, instanceIndex: number, geoTexture: boolean, ctx: RuntimeContext) {
|
||||
const obj = this.obj;
|
||||
const t = Mat4();
|
||||
const n = Mat3();
|
||||
const tmpV = Vec3();
|
||||
const stride = geoTexture ? 4 : 3;
|
||||
|
||||
const colorType = values.dColorType.ref.value;
|
||||
const tColor = values.tColor.ref.value.array;
|
||||
@@ -131,7 +145,7 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
// position
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + i });
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * 3), t);
|
||||
v3transformMat4(tmpV, v3fromArray(tmpV, vertices, i * stride), t);
|
||||
StringBuilder.writeSafe(obj, 'v ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 1000);
|
||||
StringBuilder.whitespace1(obj);
|
||||
@@ -144,7 +158,7 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
// normal
|
||||
for (let i = 0; i < vertexCount; ++i) {
|
||||
if (i % 1000 === 0 && ctx.shouldUpdate) await ctx.update({ current: currentProgress + vertexCount + i });
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * 3), n);
|
||||
v3transformMat3(tmpV, v3fromArray(tmpV, normals, i * stride), n);
|
||||
StringBuilder.writeSafe(obj, 'vn ');
|
||||
StringBuilder.writeFloat(obj, tmpV[0], 100);
|
||||
StringBuilder.whitespace1(obj);
|
||||
@@ -165,14 +179,17 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
case 'instance':
|
||||
color = Color.fromArray(tColor, instanceIndex * 3);
|
||||
break;
|
||||
case 'group':
|
||||
color = Color.fromArray(tColor, groups[indices[i]] * 3);
|
||||
case 'group': {
|
||||
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, group * 3);
|
||||
break;
|
||||
case 'groupInstance':
|
||||
}
|
||||
case 'groupInstance': {
|
||||
const groupCount = values.uGroupCount.ref.value;
|
||||
const group = groups[indices[i]];
|
||||
const group = geoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
|
||||
color = Color.fromArray(tColor, (instanceIndex * groupCount + group) * 3);
|
||||
break;
|
||||
}
|
||||
case 'vertex':
|
||||
color = Color.fromArray(tColor, i * 3);
|
||||
break;
|
||||
@@ -183,9 +200,9 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
}
|
||||
this.updateMaterial(color, uAlpha);
|
||||
|
||||
const v1 = this.vertexOffset + indices[i] + 1;
|
||||
const v2 = this.vertexOffset + indices[i + 1] + 1;
|
||||
const v3 = this.vertexOffset + indices[i + 2] + 1;
|
||||
const v1 = this.vertexOffset + (geoTexture ? i : indices![i]) + 1;
|
||||
const v2 = this.vertexOffset + (geoTexture ? i + 1 : indices![i + 1]) + 1;
|
||||
const v3 = this.vertexOffset + (geoTexture ? i + 2 : indices![i + 2]) + 1;
|
||||
StringBuilder.writeSafe(obj, 'f ');
|
||||
StringBuilder.writeInteger(obj, v1);
|
||||
StringBuilder.writeSafe(obj, '//');
|
||||
@@ -212,7 +229,7 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
const drawCount = values.drawCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, ctx);
|
||||
await this.addMeshWithColors(aPosition, aNormal, elements, aGroup, vertexCount, drawCount, values, instanceIndex, false, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +266,7 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
const normals = mesh.normalBuffer.ref.value;
|
||||
const indices = mesh.indexBuffer.ref.value;
|
||||
const groups = mesh.groupBuffer.ref.value;
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,11 +304,40 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
const normals = mesh.normalBuffer.ref.value;
|
||||
const indices = mesh.indexBuffer.ref.value;
|
||||
const groups = mesh.groupBuffer.ref.value;
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, ctx);
|
||||
await this.addMeshWithColors(vertices, normals, indices, groups, vertices.length / 3, indices.length, values, instanceIndex, false, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
add(renderObject: GraphicsRenderObject, ctx: RuntimeContext) {
|
||||
private async addTextureMesh(values: TextureMeshValues, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
const GeoExportName = 'geo-export';
|
||||
if (!webgl.namedFramebuffers[GeoExportName]) {
|
||||
webgl.namedFramebuffers[GeoExportName] = webgl.resources.framebuffer();
|
||||
}
|
||||
const framebuffer = webgl.namedFramebuffers[GeoExportName];
|
||||
|
||||
const [ width, height ] = values.uGeoTexDim.ref.value;
|
||||
const vertices = new Float32Array(width * height * 4);
|
||||
const normals = new Float32Array(width * height * 4);
|
||||
const groups = webgl.isWebGL2 ? new Uint8Array(width * height * 4) : new Float32Array(width * height * 4);
|
||||
|
||||
framebuffer.bind();
|
||||
values.tPosition.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, vertices);
|
||||
values.tNormal.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, normals);
|
||||
values.tGroup.ref.value.attachFramebuffer(framebuffer, 0);
|
||||
webgl.readPixels(0, 0, width, height, groups);
|
||||
|
||||
const vertexCount = values.uVertexCount.ref.value;
|
||||
const instanceCount = values.instanceCount.ref.value;
|
||||
const drawCount = values.drawCount.ref.value;
|
||||
|
||||
for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
|
||||
await this.addMeshWithColors(vertices, normals, undefined, groups, vertexCount, drawCount, values, instanceIndex, true, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
add(renderObject: GraphicsRenderObject, webgl: WebGLContext, ctx: RuntimeContext) {
|
||||
if (!renderObject.state.visible) return;
|
||||
|
||||
switch (renderObject.type) {
|
||||
@@ -305,6 +351,8 @@ export class ObjExporter implements RenderObjectExporter<ObjData> {
|
||||
return this.addSpheres(renderObject.values as SpheresValues, ctx);
|
||||
case 'cylinders':
|
||||
return this.addCylinders(renderObject.values as CylindersValues, ctx);
|
||||
case 'texture-mesh':
|
||||
return this.addTextureMesh(renderObject.values as TextureMeshValues, webgl, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,12 @@ class Camera implements ICamera {
|
||||
|
||||
if (changed) {
|
||||
Mat4.mul(this.projectionView, this.projection, this.view);
|
||||
Mat4.invert(this.inverseProjectionView, this.projectionView);
|
||||
if (!Mat4.tryInvert(this.inverseProjectionView, this.projectionView)) {
|
||||
Mat4.copy(this.view, this.prevView);
|
||||
Mat4.copy(this.projection, this.prevProjection);
|
||||
Mat4.mul(this.projectionView, this.projection, this.view);
|
||||
return false;
|
||||
}
|
||||
|
||||
Mat4.copy(this.prevView, this.view);
|
||||
Mat4.copy(this.prevProjection, this.projection);
|
||||
@@ -230,7 +235,7 @@ namespace Camera {
|
||||
target: Vec3.create(0, 0, 0),
|
||||
|
||||
radius: 0,
|
||||
radiusMax: 0,
|
||||
radiusMax: 10,
|
||||
fog: 50,
|
||||
clipFar: true
|
||||
};
|
||||
@@ -267,6 +272,18 @@ namespace Camera {
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function areSnapshotsEqual(a: Snapshot, b: Snapshot) {
|
||||
return a.mode === b.mode
|
||||
&& a.fov === b.fov
|
||||
&& a.radius === b.radius
|
||||
&& a.radiusMax === b.radiusMax
|
||||
&& a.fog === b.fog
|
||||
&& a.clipFar === b.clipFar
|
||||
&& Vec3.exactEquals(a.position, b.position)
|
||||
&& Vec3.exactEquals(a.up, b.up)
|
||||
&& Vec3.exactEquals(a.target, b.target);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOrtho(camera: Camera) {
|
||||
|
||||
@@ -39,6 +39,9 @@ class CameraTransitionManager {
|
||||
this._target.radius = this._target.radiusMax;
|
||||
}
|
||||
|
||||
if (this._target.radius < 0.01) this._target.radius = 0.01;
|
||||
if (this._target.radiusMax < 0.01) this._target.radiusMax = 0.01;
|
||||
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
this.finish(this._target);
|
||||
return;
|
||||
|
||||
@@ -61,11 +61,17 @@ export const Canvas3DParams = {
|
||||
}, { pivot: 'radius' }),
|
||||
viewport: PD.MappedStatic('canvas', {
|
||||
canvas: PD.Group({}),
|
||||
custom: PD.Group({
|
||||
'static-frame': PD.Group({
|
||||
x: PD.Numeric(0),
|
||||
y: PD.Numeric(0),
|
||||
width: PD.Numeric(128),
|
||||
height: PD.Numeric(128)
|
||||
}),
|
||||
'relative-frame': PD.Group({
|
||||
x: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
|
||||
y: PD.Numeric(0.33, { min: 0, max: 1, step: 0.01 }),
|
||||
width: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 }),
|
||||
height: PD.Numeric(0.5, { min: 0.01, max: 1, step: 0.01 })
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -100,7 +106,7 @@ interface Canvas3DContext {
|
||||
}
|
||||
|
||||
namespace Canvas3DContext {
|
||||
const DefaultAttribs = {
|
||||
export const DefaultAttribs = {
|
||||
/** true by default to avoid issues with Safari (Jan 2021) */
|
||||
antialias: true,
|
||||
/** true to support multiple Canvas3D objects with a single context */
|
||||
@@ -201,7 +207,7 @@ interface Canvas3D {
|
||||
*/
|
||||
commit(isSynchronous?: boolean): void
|
||||
/**
|
||||
* Funcion for external "animation" control
|
||||
* Function for external "animation" control
|
||||
* Calls commit.
|
||||
*/
|
||||
tick(t: now.Timestamp, options?: { isSynchronous?: boolean, manualDraw?: boolean }): void
|
||||
@@ -214,7 +220,11 @@ interface Canvas3D {
|
||||
/** Reset the timers, used by "animate" */
|
||||
resetTime(t: number): void
|
||||
animate(): void
|
||||
pause(): void
|
||||
/**
|
||||
* Pause animation loop and optionally any rendering
|
||||
* @param noDraw pause any rendering
|
||||
*/
|
||||
pause(noDraw?: boolean): void
|
||||
identify(x: number, y: number): PickData | undefined
|
||||
mark(loci: Representation.Loci, action: MarkerAction): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
@@ -386,8 +396,10 @@ namespace Canvas3D {
|
||||
let forceNextDraw = false;
|
||||
let forceDrawAfterAllCommited = false;
|
||||
let currentTime = 0;
|
||||
let drawPaused = false;
|
||||
|
||||
function draw(force?: boolean) {
|
||||
if (drawPaused) return;
|
||||
if (render(!!force || forceNextDraw) && notifyDidDraw) {
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
@@ -429,11 +441,13 @@ namespace Canvas3D {
|
||||
}
|
||||
|
||||
function animate() {
|
||||
drawPaused = false;
|
||||
controls.start(now());
|
||||
if (animationFrameHandle === 0) _animate();
|
||||
}
|
||||
|
||||
function pause() {
|
||||
function pause(noDraw = false) {
|
||||
drawPaused = noDraw;
|
||||
cancelAnimationFrame(animationFrameHandle);
|
||||
animationFrameHandle = 0;
|
||||
}
|
||||
@@ -805,12 +819,21 @@ namespace Canvas3D {
|
||||
y = 0;
|
||||
width = gl.drawingBufferWidth;
|
||||
height = gl.drawingBufferHeight;
|
||||
} else {
|
||||
} else if (p.viewport.name === 'static-frame') {
|
||||
x = p.viewport.params.x * webgl.pixelRatio;
|
||||
y = p.viewport.params.y * webgl.pixelRatio;
|
||||
width = p.viewport.params.width * webgl.pixelRatio;
|
||||
height = p.viewport.params.height * webgl.pixelRatio;
|
||||
y = gl.drawingBufferHeight - height - p.viewport.params.y * webgl.pixelRatio;
|
||||
width = p.viewport.params.width * webgl.pixelRatio;
|
||||
} else if (p.viewport.name === 'relative-frame') {
|
||||
x = Math.round(p.viewport.params.x * gl.drawingBufferWidth);
|
||||
height = Math.round(p.viewport.params.height * gl.drawingBufferHeight);
|
||||
y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight);
|
||||
width = Math.round(p.viewport.params.width * gl.drawingBufferWidth);
|
||||
// if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x;
|
||||
// if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1;
|
||||
// console.log({ x, y, width, height });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function syncViewport() {
|
||||
|
||||
@@ -128,8 +128,8 @@ export class PickHelper {
|
||||
this.pickX = Math.ceil(x * this.pickScale);
|
||||
this.pickY = Math.ceil(y * this.pickScale);
|
||||
|
||||
const pickWidth = Math.ceil(width * this.pickScale);
|
||||
const pickHeight = Math.ceil(height * this.pickScale);
|
||||
const pickWidth = Math.floor(width * this.pickScale);
|
||||
const pickHeight = Math.floor(height * this.pickScale);
|
||||
|
||||
if (pickWidth !== this.pickWidth || pickHeight !== this.pickHeight) {
|
||||
this.pickWidth = pickWidth;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -13,7 +13,7 @@ import { Texture } from '../../mol-gl/webgl/texture';
|
||||
import { ValueCell } from '../../mol-util';
|
||||
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
|
||||
import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable';
|
||||
import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { RenderTarget } from '../../mol-gl/webgl/render-target';
|
||||
import { DrawPass } from './draw';
|
||||
@@ -70,6 +70,7 @@ const SsaoSchema = {
|
||||
|
||||
uProjection: UniformSpec('m4'),
|
||||
uInvProjection: UniformSpec('m4'),
|
||||
uBounds: UniformSpec('v4'),
|
||||
|
||||
uTexSize: UniformSpec('v2'),
|
||||
|
||||
@@ -89,6 +90,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
|
||||
|
||||
uProjection: ValueCell.create(Mat4.identity()),
|
||||
uInvProjection: ValueCell.create(Mat4.identity()),
|
||||
uBounds: ValueCell.create(Vec4()),
|
||||
|
||||
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
|
||||
|
||||
@@ -118,6 +120,7 @@ const SsaoBlurSchema = {
|
||||
|
||||
uNear: UniformSpec('f'),
|
||||
uFar: UniformSpec('f'),
|
||||
uBounds: UniformSpec('v4'),
|
||||
dOrthographic: DefineSpec('number'),
|
||||
};
|
||||
|
||||
@@ -139,6 +142,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
|
||||
|
||||
uNear: ValueCell.create(0.0),
|
||||
uFar: ValueCell.create(10000.0),
|
||||
uBounds: ValueCell.create(Vec4()),
|
||||
dOrthographic: ValueCell.create(0),
|
||||
};
|
||||
|
||||
@@ -286,10 +290,14 @@ export class PostprocessingPass {
|
||||
|
||||
private readonly renderable: PostprocessingRenderable
|
||||
|
||||
private scale: number
|
||||
private ssaoScale: number
|
||||
private calcSsaoScale() {
|
||||
// downscale ssao for high pixel-ratios
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio);
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
|
||||
this.scale = 1 / this.webgl.pixelRatio;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
const width = colorTarget.getWidth();
|
||||
@@ -298,7 +306,7 @@ export class PostprocessingPass {
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'nearest');
|
||||
|
||||
this.outlinesTarget = webgl.createRenderTarget(width, height, false);
|
||||
this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
|
||||
@@ -317,14 +325,14 @@ export class PostprocessingPass {
|
||||
this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
|
||||
this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
|
||||
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
|
||||
|
||||
@@ -338,9 +346,13 @@ export class PostprocessingPass {
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
if (width !== w || height !== h) {
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
const ssaoScale = this.calcSsaoScale();
|
||||
|
||||
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
|
||||
this.ssaoScale = ssaoScale;
|
||||
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
@@ -349,8 +361,8 @@ export class PostprocessingPass {
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,8 +379,22 @@ export class PostprocessingPass {
|
||||
Mat4.invert(invProjection, camera.projection);
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection);
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection);
|
||||
ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
|
||||
ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
|
||||
|
||||
const [w, h] = this.renderable.values.uTexSize.ref.value;
|
||||
const b = this.ssaoRenderable.values.uBounds;
|
||||
const v = camera.viewport;
|
||||
const s = this.ssaoScale;
|
||||
Vec4.set(b.ref.value,
|
||||
Math.floor(v.x * s) / (w * s),
|
||||
Math.floor(v.y * s) / (h * s),
|
||||
Math.ceil((v.x + v.width) * s) / (w * s),
|
||||
Math.ceil((v.y + v.height) * s) / (h * s)
|
||||
);
|
||||
ValueCell.update(b, b.ref.value);
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBounds, b.ref.value);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBounds, b.ref.value);
|
||||
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near);
|
||||
@@ -376,7 +402,9 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far);
|
||||
|
||||
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; }
|
||||
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
needsUpdateSsaoBlur = true;
|
||||
}
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic);
|
||||
|
||||
@@ -384,7 +412,7 @@ export class PostprocessingPass {
|
||||
needsUpdateSsao = true;
|
||||
|
||||
this.nSamples = props.occlusion.params.samples;
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
|
||||
ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples));
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples);
|
||||
}
|
||||
ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
|
||||
@@ -394,10 +422,10 @@ export class PostprocessingPass {
|
||||
needsUpdateSsaoBlur = true;
|
||||
|
||||
this.blurKernelSize = props.occlusion.params.blurKernelSize;
|
||||
let kernel = getBlurKernel(this.blurKernelSize);
|
||||
const kernel = getBlurKernel(this.blurKernelSize);
|
||||
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
@@ -467,10 +495,10 @@ export class PostprocessingPass {
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sx = Math.floor(x * this.scale);
|
||||
const sy = Math.floor(y * this.scale);
|
||||
const sw = Math.floor(width * this.scale);
|
||||
const sh = Math.floor(height * this.scale);
|
||||
const sx = Math.floor(x * this.ssaoScale);
|
||||
const sy = Math.floor(y * this.ssaoScale);
|
||||
const sw = Math.ceil(width * this.ssaoScale);
|
||||
const sh = Math.ceil(height * this.ssaoScale);
|
||||
this.webgl.gl.viewport(sx, sy, sw, sh);
|
||||
this.webgl.gl.scissor(sx, sy, sw, sh);
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export interface GeometryUtils<G extends Geometry, P extends PD.Params = Geometr
|
||||
createValuesSimple(geometry: G, props: Partial<PD.Values<P>>, colorValue: Color, sizeValue: number, transform?: TransformData): V
|
||||
updateValues(values: V, props: PD.Values<P>): void
|
||||
updateBoundingSphere(values: V, geometry: G): void
|
||||
createRenderableState(props: Partial<PD.Values<P>>): RenderableState
|
||||
createRenderableState(props: PD.Values<P>): RenderableState
|
||||
updateRenderableState(state: RenderableState, props: PD.Values<P>): void
|
||||
createPositionIterator(geometry: G, transform: TransformData): LocationIterator
|
||||
}
|
||||
|
||||
@@ -144,6 +144,14 @@ export namespace MeshBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export function addMesh(state: State, t: Mat4, mesh: Mesh) {
|
||||
addPrimitive(state, t, {
|
||||
vertices: mesh.vertexBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
|
||||
normals: mesh.normalBuffer.ref.value.subarray(0, mesh.vertexCount * 3),
|
||||
indices: mesh.indexBuffer.ref.value.subarray(0, mesh.triangleCount * 3),
|
||||
});
|
||||
}
|
||||
|
||||
export function getMesh (state: State): Mesh {
|
||||
const { vertices, normals, indices, groups, mesh } = state;
|
||||
const vb = ChunkedArray.compact(vertices, true) as Float32Array;
|
||||
|
||||
78
src/mol-geo/primitive/torus.ts
Normal file
78
src/mol-geo/primitive/torus.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
// adapted from three.js, MIT License Copyright 2010-2021 three.js authors
|
||||
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { Primitive } from './primitive';
|
||||
|
||||
export const DefaultTorusProps = {
|
||||
radius: 1,
|
||||
tube: 0.4,
|
||||
radialSegments: 8,
|
||||
tubularSegments: 6,
|
||||
arc: Math.PI * 2,
|
||||
};
|
||||
export type TorusProps = Partial<typeof DefaultTorusProps>
|
||||
|
||||
export function Torus(props?: TorusProps): Primitive {
|
||||
const { radius, tube, radialSegments, tubularSegments, arc } = { ...DefaultTorusProps, ...props };
|
||||
|
||||
// buffers
|
||||
const indices: number[] = [];
|
||||
const vertices: number[] = [];
|
||||
const normals: number[] = [];
|
||||
|
||||
// helper variables
|
||||
const center = Vec3();
|
||||
const vertex = Vec3();
|
||||
const normal = Vec3();
|
||||
|
||||
// generate vertices and normals
|
||||
for (let j = 0; j <= radialSegments; ++j) {
|
||||
for (let i = 0; i <= tubularSegments; ++i) {
|
||||
const u = i / tubularSegments * arc;
|
||||
const v = j / radialSegments * Math.PI * 2;
|
||||
|
||||
// vertex
|
||||
Vec3.set(
|
||||
vertex,
|
||||
(radius + tube * Math.cos(v)) * Math.cos(u),
|
||||
(radius + tube * Math.cos(v)) * Math.sin(u),
|
||||
tube * Math.sin(v)
|
||||
);
|
||||
vertices.push(...vertex);
|
||||
|
||||
// normal
|
||||
Vec3.set(center, radius * Math.cos(u), radius * Math.sin(u), 0 );
|
||||
Vec3.sub(normal, vertex, center);
|
||||
Vec3.normalize(normal, normal);
|
||||
normals.push(...normal);
|
||||
}
|
||||
}
|
||||
|
||||
// generate indices
|
||||
for (let j = 1; j <= radialSegments; ++j) {
|
||||
for (let i = 1; i <= tubularSegments; ++i) {
|
||||
|
||||
// indices
|
||||
const a = (tubularSegments + 1) * j + i - 1;
|
||||
const b = (tubularSegments + 1) * (j - 1) + i - 1;
|
||||
const c = (tubularSegments + 1) * (j - 1) + i;
|
||||
const d = (tubularSegments + 1) * j + i;
|
||||
|
||||
// faces
|
||||
indices.push(a, b, d);
|
||||
indices.push(b, c, d);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
vertices: new Float32Array(vertices),
|
||||
normals: new Float32Array(normals),
|
||||
indices: new Uint32Array(indices)
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -88,7 +88,8 @@ float getSsao(vec2 coords) {
|
||||
} else if (rawSsao > 0.001) {
|
||||
return rawSsao;
|
||||
}
|
||||
return 0.0;
|
||||
// treat values close to 0.0 as errors and return no occlusion
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export const ssaoBlur_frag = `
|
||||
@@ -11,6 +12,7 @@ precision highp sampler2D;
|
||||
|
||||
uniform sampler2D tSsaoDepth;
|
||||
uniform vec2 uTexSize;
|
||||
uniform vec4 uBounds;
|
||||
|
||||
uniform float uKernel[dOcclusionKernelSize];
|
||||
|
||||
@@ -36,16 +38,25 @@ bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
bool outsideBounds(const in vec2 p) {
|
||||
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec2 coords = gl_FragCoord.xy / uTexSize;
|
||||
|
||||
vec2 packedDepth = texture2D(tSsaoDepth, coords).zw;
|
||||
|
||||
if (outsideBounds(coords)) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
float selfDepth = unpackRGToUnitInterval(packedDepth);
|
||||
// if background and if second pass
|
||||
if (isBackground(selfDepth) && uBlurDirectionY != 0.0) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
|
||||
return;
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
float selfViewZ = getViewZ(selfDepth);
|
||||
@@ -57,6 +68,9 @@ void main(void) {
|
||||
// only if kernelSize is odd
|
||||
for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) {
|
||||
vec2 sampleCoords = coords + float(i) * offset;
|
||||
if (outsideBounds(sampleCoords)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vec4 sampleSsaoDepth = texture2D(tSsaoDepth, sampleCoords);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -13,14 +13,14 @@ precision highp sampler2D;
|
||||
#include common
|
||||
|
||||
uniform sampler2D tDepth;
|
||||
uniform vec2 uTexSize;
|
||||
uniform vec4 uBounds;
|
||||
|
||||
uniform vec3 uSamples[dNSamples];
|
||||
|
||||
uniform mat4 uProjection;
|
||||
uniform mat4 uInvProjection;
|
||||
|
||||
uniform vec2 uTexSize;
|
||||
|
||||
uniform float uRadius;
|
||||
uniform float uBias;
|
||||
|
||||
@@ -46,8 +46,12 @@ bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
bool outsideBounds(const in vec2 p) {
|
||||
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
|
||||
}
|
||||
|
||||
float getDepth(const in vec2 coords) {
|
||||
return unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
return outsideBounds(coords) ? 1.0 : unpackRGBAToDepth(texture2D(tDepth, coords));
|
||||
}
|
||||
|
||||
vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) {
|
||||
|
||||
@@ -333,7 +333,7 @@ namespace Mat4 {
|
||||
return out;
|
||||
}
|
||||
|
||||
export function invert(out: Mat4, a: Mat4) {
|
||||
export function tryInvert(out: Mat4, a: Mat4) {
|
||||
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3],
|
||||
a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7],
|
||||
a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
|
||||
@@ -356,8 +356,7 @@ namespace Mat4 {
|
||||
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
||||
|
||||
if (!det) {
|
||||
console.warn('non-invertible matrix.', a);
|
||||
return out;
|
||||
return false;
|
||||
}
|
||||
det = 1.0 / det;
|
||||
|
||||
@@ -378,6 +377,13 @@ namespace Mat4 {
|
||||
out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
|
||||
out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function invert(out: Mat4, a: Mat4) {
|
||||
if (!tryInvert(out, a)) {
|
||||
console.warn('non-invertible matrix.', a);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BoundaryHelper } from '../../../mol-math/geometry/boundary-helper';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { PrincipalAxes } from '../../../mol-math/linear-algebra/matrix/principal-axes';
|
||||
import { EmptyLoci, Loci } from '../../../mol-model/loci';
|
||||
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
|
||||
import { QueryContext, Structure, StructureElement, StructureQuery, StructureSelection } from '../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
@@ -457,6 +457,13 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
|
||||
this.triggerInteraction(modifier, loci, applyGranularity);
|
||||
}
|
||||
|
||||
fromCompiledQuery(modifier: StructureSelectionModifier, query: StructureQuery, applyGranularity = true) {
|
||||
for (const s of this.applicableStructures) {
|
||||
const loci = query(new QueryContext(s));
|
||||
this.triggerInteraction(modifier, StructureSelection.toLociWithSourceUnits(loci), applyGranularity);
|
||||
}
|
||||
}
|
||||
|
||||
fromSelectionQuery(modifier: StructureSelectionModifier, query: StructureSelectionQuery, applyGranularity = true) {
|
||||
this.plugin.runTask(Task.create('Structure Selection', async runtime => {
|
||||
for (const s of this.applicableStructures) {
|
||||
|
||||
@@ -17,13 +17,14 @@ export function buildVolumeHierarchy(state: State, previous?: VolumeHierarchy) {
|
||||
|
||||
export interface VolumeHierarchy {
|
||||
volumes: VolumeRef[],
|
||||
lazyVolumes: LazyVolumeRef[],
|
||||
refs: Map<StateTransform.Ref, VolumeHierarchyRef>
|
||||
// TODO: might be needed in the future
|
||||
// decorators: Map<StateTransform.Ref, StateTransform>,
|
||||
}
|
||||
|
||||
export function VolumeHierarchy(): VolumeHierarchy {
|
||||
return { volumes: [], refs: new Map() };
|
||||
return { volumes: [], lazyVolumes: [], refs: new Map() };
|
||||
}
|
||||
|
||||
interface RefBase<K extends string = string, O extends StateObject = StateObject, T extends StateTransformer = StateTransformer> {
|
||||
@@ -32,7 +33,7 @@ interface RefBase<K extends string = string, O extends StateObject = StateObject
|
||||
version: StateTransform['version']
|
||||
}
|
||||
|
||||
export type VolumeHierarchyRef = VolumeRef | VolumeRepresentationRef
|
||||
export type VolumeHierarchyRef = VolumeRef | LazyVolumeRef | VolumeRepresentationRef
|
||||
|
||||
export interface VolumeRef extends RefBase<'volume', SO.Volume.Data> {
|
||||
representations: VolumeRepresentationRef[]
|
||||
@@ -42,6 +43,13 @@ function VolumeRef(cell: StateObjectCell<SO.Volume.Data>): VolumeRef {
|
||||
return { kind: 'volume', cell, version: cell.transform.version, representations: [] };
|
||||
}
|
||||
|
||||
export interface LazyVolumeRef extends RefBase<'lazy-volume', SO.Volume.Lazy> {
|
||||
}
|
||||
|
||||
function LazyVolumeRef(cell: StateObjectCell<SO.Volume.Lazy>): LazyVolumeRef {
|
||||
return { kind: 'lazy-volume', cell, version: cell.transform.version };
|
||||
}
|
||||
|
||||
export interface VolumeRepresentationRef extends RefBase<'volume-representation', SO.Volume.Representation3D, StateTransforms['Representation']['VolumeRepresentation3D']> {
|
||||
volume: VolumeRef
|
||||
}
|
||||
@@ -95,6 +103,10 @@ const Mapping: [TestCell, ApplyRef, LeaveRef][] = [
|
||||
state.currentVolume = createOrUpdateRefList(state, cell, state.hierarchy.volumes, VolumeRef, cell);
|
||||
}, state => state.currentVolume = void 0],
|
||||
|
||||
[cell => SO.Volume.Lazy.is(cell.obj), (state, cell) => {
|
||||
createOrUpdateRefList(state, cell, state.hierarchy.lazyVolumes, LazyVolumeRef, cell);
|
||||
}, noop],
|
||||
|
||||
[(cell, state) => {
|
||||
return !cell.state.isGhost && !!state.currentVolume && SO.Volume.Representation3D.is(cell.obj);
|
||||
}, (state, cell) => {
|
||||
|
||||
@@ -22,6 +22,8 @@ import { VolumeRepresentation } from '../mol-repr/volume/representation';
|
||||
import { StateObject, StateTransformer } from '../mol-state';
|
||||
import { CubeFile } from '../mol-io/reader/cube/parser';
|
||||
import { DxFile } from '../mol-io/reader/dx/parser';
|
||||
import { Color } from '../mol-util/color/color';
|
||||
import { Asset } from '../mol-util/assets';
|
||||
|
||||
export type TypeClass = 'root' | 'data' | 'prop'
|
||||
|
||||
@@ -119,7 +121,21 @@ export namespace PluginStateObject {
|
||||
}
|
||||
|
||||
export namespace Volume {
|
||||
export interface LazyInfo {
|
||||
url: string | Asset.Url,
|
||||
isBinary: boolean,
|
||||
format: string,
|
||||
entryId?: string,
|
||||
isovalues: {
|
||||
type: 'absolute' | 'relative',
|
||||
value: number,
|
||||
color: Color,
|
||||
alpha?: number
|
||||
}[]
|
||||
}
|
||||
|
||||
export class Data extends Create<_Volume>({ name: 'Volume', typeClass: 'Object' }) { }
|
||||
export class Lazy extends Create<LazyInfo>({ name: 'Lazy Volume', typeClass: 'Object' }) { }
|
||||
export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>, _Volume>({ name: 'Volume 3D' }) { }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { PluginStateObject as SO, PluginStateTransform } from '../objects';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { parseCube } from '../../mol-io/reader/cube/parser';
|
||||
import { parseDx } from '../../mol-io/reader/dx/parser';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
|
||||
export { Download };
|
||||
export { DownloadBlob };
|
||||
@@ -35,6 +36,7 @@ export { ParseDx };
|
||||
export { ImportString };
|
||||
export { ImportJson };
|
||||
export { ParseJson };
|
||||
export { LazyVolume };
|
||||
|
||||
type Download = typeof Download
|
||||
const Download = PluginStateTransform.BuiltIn({
|
||||
@@ -441,4 +443,31 @@ const ParseJson = PluginStateTransform.BuiltIn({
|
||||
return new SO.Format.Json(json);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
type LazyVolume = typeof LazyVolume
|
||||
const LazyVolume = PluginStateTransform.BuiltIn({
|
||||
name: 'lazy-volume',
|
||||
display: { name: 'Lazy Volume', description: 'A placeholder for lazy loaded volume representation' },
|
||||
from: SO.Root,
|
||||
to: SO.Volume.Lazy,
|
||||
params: {
|
||||
url: PD.Url(''),
|
||||
isBinary: PD.Boolean(false),
|
||||
format: PD.Text('ccp4'), // TODO: use Select based on available formats
|
||||
entryId: PD.Text(''),
|
||||
isovalues: PD.ObjectList({
|
||||
type: PD.Text<'absolute' | 'relative'>('relative'), // TODO: Select
|
||||
value: PD.Numeric(0),
|
||||
color: PD.Color(ColorNames.black),
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 })
|
||||
}, e => `${e.type} ${e.value}`)
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Lazy Volume', async ctx => {
|
||||
return new SO.Volume.Lazy(params, { label: `${params.entryId || params.url}`, description: 'Lazy Volume' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,22 +6,24 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { Structure } from '../../mol-model/structure/structure/structure';
|
||||
import { getElementQueries, getNonStandardResidueQueries, getPolymerAndBranchedEntityQueries, StructureSelectionQueries, StructureSelectionQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
|
||||
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
|
||||
import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { StructureComponentRef, StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { compileResidueListSelection } from '../../mol-script/util/residue-list';
|
||||
import { memoizeLatest } from '../../mol-util/memoize';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
import { stripTags } from '../../mol-util/string';
|
||||
import { capitalize, stripTags } from '../../mol-util/string';
|
||||
import { PluginUIComponent, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/common';
|
||||
import { BrushSvg, CancelOutlinedSvg, CloseSvg, CubeOutlineSvg, HelpOutlineSvg, Icon, IntersectSvg, RemoveSvg, RestoreSvg, SelectionModeSvg, SetSvg, SubtractSvg, UnionSvg } from '../controls/icons';
|
||||
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
|
||||
import { UnionSvg, SubtractSvg, IntersectSvg, SetSvg, CubeOutlineSvg, Icon, SelectionModeSvg, RemoveSvg, RestoreSvg, HelpOutlineSvg, CancelOutlinedSvg, BrushSvg, CloseSvg } from '../controls/icons';
|
||||
import { HelpGroup, HelpText, ViewportHelpContent } from '../viewport/help';
|
||||
import { AddComponentControls } from './components';
|
||||
import { Structure } from '../../mol-model/structure/structure/structure';
|
||||
import { ViewportHelpContent, HelpGroup, HelpText } from '../viewport/help';
|
||||
|
||||
|
||||
export class ToggleSelectionModeButton extends PurePluginUIComponent<{ inline?: boolean }> {
|
||||
@@ -47,12 +49,15 @@ const StructureSelectionParams = {
|
||||
granularity: InteractivityManager.Params.granularity,
|
||||
};
|
||||
|
||||
type SelectionHelperType = 'residue-list'
|
||||
|
||||
interface StructureSelectionActionsControlsState {
|
||||
isEmpty: boolean,
|
||||
isBusy: boolean,
|
||||
canUndo: boolean,
|
||||
|
||||
action?: StructureSelectionModifier | 'theme' | 'add-component' | 'help'
|
||||
action?: StructureSelectionModifier | 'theme' | 'add-component' | 'help',
|
||||
helper?: SelectionHelperType,
|
||||
}
|
||||
|
||||
const ActionHeader = new Map<StructureSelectionModifier, string>([
|
||||
@@ -65,6 +70,7 @@ const ActionHeader = new Map<StructureSelectionModifier, string>([
|
||||
export class StructureSelectionActionsControls extends PluginUIComponent<{}, StructureSelectionActionsControlsState> {
|
||||
state = {
|
||||
action: void 0 as StructureSelectionActionsControlsState['action'],
|
||||
helper: void 0 as StructureSelectionActionsControlsState['helper'],
|
||||
|
||||
isEmpty: true,
|
||||
isBusy: false,
|
||||
@@ -118,7 +124,16 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
}
|
||||
}
|
||||
|
||||
get structures () {
|
||||
selectHelper: ActionMenu.OnSelect = (item, e) => {
|
||||
console.log(item);
|
||||
if (!item || !this.state.action) {
|
||||
this.setState({ action: void 0, helper: void 0 });
|
||||
return;
|
||||
}
|
||||
this.setState({ helper: (item.value as { kind: SelectionHelperType }).kind });
|
||||
}
|
||||
|
||||
get structures() {
|
||||
const structures: Structure[] = [];
|
||||
for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
|
||||
const structure = s.cell.obj?.data;
|
||||
@@ -129,7 +144,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
|
||||
private queriesItems: ActionMenu.Items[] = []
|
||||
private queriesVersion = -1
|
||||
get queries () {
|
||||
get queries() {
|
||||
const { registry } = this.plugin.query.structure;
|
||||
if (registry.version !== this.queriesVersion) {
|
||||
const structures = this.structures;
|
||||
@@ -150,8 +165,25 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
return this.queriesItems;
|
||||
}
|
||||
|
||||
private helpersItems?: ActionMenu.Items[] = void 0;
|
||||
get helpers() {
|
||||
if (this.helpersItems) return this.helpersItems;
|
||||
// TODO: this is an initial implementation of the helper UI
|
||||
// the plan is to add support to input queries in different languages
|
||||
// after this has been implemented in mol-script
|
||||
const helpers = [
|
||||
{ kind: 'residue-list' as SelectionHelperType, category: 'Helpers', label: 'Residue List', description: 'Create a selection from a list of residue ranges.' }
|
||||
];
|
||||
this.helpersItems = ActionMenu.createItems(helpers, {
|
||||
label: q => q.label,
|
||||
category: q => q.category,
|
||||
description: q => q.description
|
||||
});
|
||||
return this.helpersItems;
|
||||
}
|
||||
|
||||
private showAction(q: StructureSelectionActionsControlsState['action']) {
|
||||
return () => this.setState({ action: this.state.action === q ? void 0 : q });
|
||||
return () => this.setState({ action: this.state.action === q ? void 0 : q, helper: void 0 });
|
||||
}
|
||||
|
||||
toggleAdd = this.showAction('add')
|
||||
@@ -187,6 +219,45 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
? `Undo ${this.plugin.state.data.latestUndoLabel}`
|
||||
: 'Some mistakes of the past can be undone.';
|
||||
|
||||
let children: React.ReactNode | undefined = void 0;
|
||||
|
||||
if (this.state.action && !this.state.helper) {
|
||||
children = <>
|
||||
{(this.state.action && this.state.action !== 'theme' && this.state.action !== 'add-component' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
|
||||
<ActionMenu items={this.helpers} onSelect={this.selectHelper} noOffset />
|
||||
</div>}
|
||||
{this.state.action === 'theme' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Theme' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleTheme} topRightIcon={CloseSvg}>
|
||||
<ApplyThemeControls onApply={this.toggleTheme} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'add-component' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Add Component' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddComponent} topRightIcon={CloseSvg}>
|
||||
<AddComponentControls onApply={this.toggleAddComponent} forSelection />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={CloseSvg} maxHeight='300px'>
|
||||
<HelpGroup header='Selection Operations'>
|
||||
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
|
||||
</HelpGroup>
|
||||
<HelpGroup header='Representation Operations'>
|
||||
<HelpText>Use <Icon svg={BrushSvg} inline /> <Icon svg={CubeOutlineSvg} inline /> <Icon svg={RemoveSvg} inline /> <Icon svg={RestoreSvg} inline /> to color, create components, remove from components, or undo actions.</HelpText>
|
||||
</HelpGroup>
|
||||
<ViewportHelpContent selectOnly={true} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
</>;
|
||||
} else if (ActionHeader.has(this.state.action as any) && this.state.helper === 'residue-list') {
|
||||
const close = () => this.setState({ action: void 0, helper: void 0 });
|
||||
children = <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Residue List' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={close} topRightIcon={CloseSvg}>
|
||||
<ResidueListSelectionHelper modifier={this.state.action as any} plugin={this.plugin} close={close} />
|
||||
</ControlGroup>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className='msp-flex-row' style={{ background: 'none' }}>
|
||||
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
|
||||
@@ -195,7 +266,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
<ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
|
||||
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
|
||||
|
||||
<ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
|
||||
<ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
|
||||
<ToggleButton icon={CubeOutlineSvg} title='Create Component of Selection with Representation' toggle={this.toggleAddComponent} isSelected={this.state.action === 'add-component'} disabled={this.isDisabled} />
|
||||
<IconButton svg={RemoveSvg} title='Remove/subtract Selection from all Components' onClick={this.subtract} disabled={this.isDisabled} />
|
||||
<IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
|
||||
@@ -203,30 +274,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
|
||||
<ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />
|
||||
<IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />
|
||||
</div>
|
||||
{(this.state.action && this.state.action !== 'theme' && this.state.action !== 'add-component' && this.state.action !== 'help') && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
|
||||
</div>}
|
||||
{this.state.action === 'theme' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Theme' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleTheme} topRightIcon={CloseSvg}>
|
||||
<ApplyThemeControls onApply={this.toggleTheme} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'add-component' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Add Component' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddComponent} topRightIcon={CloseSvg}>
|
||||
<AddComponentControls onApply={this.toggleAddComponent} forSelection />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
|
||||
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={CloseSvg} maxHeight='300px'>
|
||||
<HelpGroup header='Selection Operations'>
|
||||
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
|
||||
</HelpGroup>
|
||||
<HelpGroup header='Representation Operations'>
|
||||
<HelpText>Use <Icon svg={BrushSvg} inline /> <Icon svg={CubeOutlineSvg} inline /> <Icon svg={RemoveSvg} inline /> <Icon svg={RestoreSvg} inline /> to color, create components, remove from components, or undo actions.</HelpText>
|
||||
</HelpGroup>
|
||||
<ViewportHelpContent selectOnly={true} />
|
||||
</ControlGroup>
|
||||
</div>}
|
||||
{children}
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -333,4 +381,34 @@ class ApplyThemeControls extends PurePluginUIComponent<ApplyThemeControlsProps,
|
||||
</Button>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
const ResidueListIdTypeParams = {
|
||||
idType: ParamDefinition.Select<'auth' | 'label'>('auth', ParamDefinition.arrayToOptions(['auth', 'label'])),
|
||||
residues: ParamDefinition.Text('', { description: 'A comma separated list of residue ranges in given chain, e.g. A 10-15, B 25, C 30:i' })
|
||||
};
|
||||
|
||||
const DefaultResidueListIdTypeParams = ParamDefinition.getDefaultValues(ResidueListIdTypeParams);
|
||||
|
||||
function ResidueListSelectionHelper({ modifier, plugin, close }: { modifier: StructureSelectionModifier, plugin: PluginContext, close: () => void }) {
|
||||
const [state, setState] = React.useState(DefaultResidueListIdTypeParams);
|
||||
|
||||
const apply = () => {
|
||||
if (state.residues.length === 0) return;
|
||||
|
||||
try {
|
||||
close();
|
||||
const query = compileResidueListSelection(state.residues, state.idType);
|
||||
plugin.managers.structure.selection.fromCompiledQuery(modifier, query, false);
|
||||
} catch (e) {
|
||||
plugin.log.error(`Failed to create selection: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
return <>
|
||||
<ParameterControls params={ResidueListIdTypeParams} values={state} onChangeValues={setState} onEnter={apply} />
|
||||
<Button className='msp-btn-commit msp-btn-commit-on' disabled={state.residues.length === 0} onClick={apply} style={{ marginTop: '1px' }}>
|
||||
{capitalize(modifier)} Selection
|
||||
</Button>
|
||||
</>;
|
||||
}
|
||||
@@ -8,11 +8,11 @@
|
||||
import * as React from 'react';
|
||||
import { StructureHierarchyManager } from '../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { VolumeHierarchyManager } from '../../mol-plugin-state/manager/volume/hierarchy';
|
||||
import { VolumeRef, VolumeRepresentationRef } from '../../mol-plugin-state/manager/volume/hierarchy-state';
|
||||
import { LazyVolumeRef, VolumeRef, VolumeRepresentationRef } from '../../mol-plugin-state/manager/volume/hierarchy-state';
|
||||
import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation';
|
||||
import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior';
|
||||
import { InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/transformers';
|
||||
import { State, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { State, StateObjectCell, StateObjectSelector, StateSelection, StateTransform } from '../../mol-state';
|
||||
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
|
||||
import { ActionMenu } from '../controls/action-menu';
|
||||
import { Button, ExpandGroup, IconButton } from '../controls/common';
|
||||
@@ -21,6 +21,9 @@ import { UpdateTransformControl } from '../state/update-transform';
|
||||
import { BindingsHelp } from '../viewport/help';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { BlurOnSvg, ErrorSvg, CheckSvg, AddSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, DeleteOutlinedSvg, MoreHorizSvg } from '../controls/icons';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params';
|
||||
|
||||
interface VolumeStreamingControlState extends CollapsableState {
|
||||
isBusy: boolean
|
||||
@@ -104,6 +107,7 @@ export class VolumeStreamingControls extends CollapsableControls<{}, VolumeStrea
|
||||
|
||||
interface VolumeSourceControlState extends CollapsableState {
|
||||
isBusy: boolean,
|
||||
loadingLabel?: string,
|
||||
show?: 'hierarchy' | 'add-repr'
|
||||
}
|
||||
|
||||
@@ -120,18 +124,23 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.managers.volume.hierarchy.behaviors.selection, sel => {
|
||||
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 });
|
||||
this.setState({ isHidden: sel.hierarchy.volumes.length === 0 && sel.hierarchy.lazyVolumes.length === 0 });
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => {
|
||||
this.setState({ isBusy: v });
|
||||
});
|
||||
}
|
||||
|
||||
private item = (ref: VolumeRef) => {
|
||||
private item = (ref: VolumeRef | LazyVolumeRef) => {
|
||||
const selected = this.plugin.managers.volume.hierarchy.selection;
|
||||
|
||||
const label = ref.cell.obj?.label || 'Volume';
|
||||
const item: ActionMenu.Item = { kind: 'item', label: label || ref.kind, selected: selected === ref, value: ref };
|
||||
const item: ActionMenu.Item = {
|
||||
kind: 'item',
|
||||
label: (ref.kind === 'lazy-volume' ? 'Load ' : '') + (label || ref.kind),
|
||||
selected: selected === ref,
|
||||
value: ref
|
||||
};
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -139,9 +148,15 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
const mng = this.plugin.managers.volume.hierarchy;
|
||||
const { current } = mng;
|
||||
const ret: ActionMenu.Items = [];
|
||||
for (let ref of current.volumes) {
|
||||
|
||||
for (const ref of current.volumes) {
|
||||
ret.push(this.item(ref));
|
||||
}
|
||||
|
||||
for (const ref of current.lazyVolumes) {
|
||||
ret.push(this.item(ref));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -158,11 +173,13 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
const { volumes } = this.plugin.managers.volume.hierarchy.current;
|
||||
return volumes.length === 0;
|
||||
const { volumes, lazyVolumes } = this.plugin.managers.volume.hierarchy.current;
|
||||
return volumes.length === 0 && lazyVolumes.length === 0;
|
||||
}
|
||||
|
||||
get label() {
|
||||
if (this.state.loadingLabel) return `Loading ${this.state.loadingLabel}...`;
|
||||
|
||||
const selected = this.plugin.managers.volume.hierarchy.selection;
|
||||
if (!selected) return 'Nothing Selected';
|
||||
return selected?.cell.obj?.label || 'Volume';
|
||||
@@ -171,7 +188,45 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
selectCurrent: ActionMenu.OnSelect = (item) => {
|
||||
this.toggleHierarchy();
|
||||
if (!item) return;
|
||||
this.plugin.managers.volume.hierarchy.setCurrent(item.value as VolumeRef);
|
||||
|
||||
const current = item.value as VolumeRef | LazyVolumeRef;
|
||||
if (current.kind === 'volume') {
|
||||
this.plugin.managers.volume.hierarchy.setCurrent(current);
|
||||
} else {
|
||||
this.lazyLoad(current.cell);
|
||||
}
|
||||
}
|
||||
|
||||
private async lazyLoad(cell: StateObjectCell<PluginStateObject.Volume.Lazy>) {
|
||||
const { url, isBinary, format, entryId, isovalues } = cell.obj!.data;
|
||||
|
||||
this.setState({ isBusy: true, loadingLabel: cell.obj!.label });
|
||||
|
||||
try {
|
||||
const plugin = this.plugin;
|
||||
await plugin.dataTransaction(async () => {
|
||||
const data = await plugin.builders.data.download({ url, isBinary, label: entryId }, { state: { isGhost: true } });
|
||||
const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId });
|
||||
const volume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!volume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const repr = plugin.build().to(volume);
|
||||
for (const iso of isovalues) {
|
||||
repr.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, volume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } },
|
||||
color: 'uniform',
|
||||
colorParams: { value: iso.color }
|
||||
}));
|
||||
}
|
||||
|
||||
await repr.commit();
|
||||
|
||||
await plugin.build().delete(cell).commit();
|
||||
});
|
||||
} finally {
|
||||
this.setState({ isBusy: false, loadingLabel: void 0 });
|
||||
}
|
||||
}
|
||||
|
||||
selectAdd: ActionMenu.OnSelect = (item) => {
|
||||
@@ -186,13 +241,12 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo
|
||||
renderControls() {
|
||||
const disabled = this.state.isBusy || this.isEmpty;
|
||||
const label = this.label;
|
||||
|
||||
const selected = this.plugin.managers.volume.hierarchy.selection;
|
||||
|
||||
return <>
|
||||
<div className='msp-flex-row' style={{ marginTop: '1px' }}>
|
||||
<Button noOverflow flex onClick={this.toggleHierarchy} disabled={disabled} title={label}>{label}</Button>
|
||||
{!this.isEmpty && <IconButton svg={AddSvg} onClick={this.toggleAddRepr} title='Apply a structure presets to the current hierarchy.' toggleState={this.state.show === 'add-repr'} disabled={disabled} />}
|
||||
{!this.isEmpty && selected && <IconButton svg={AddSvg} onClick={this.toggleAddRepr} title='Apply a structure presets to the current hierarchy.' toggleState={this.state.show === 'add-repr'} disabled={disabled} />}
|
||||
</div>
|
||||
{this.state.show === 'hierarchy' && <ActionMenu items={this.hierarchyItems} onSelect={this.selectCurrent} />}
|
||||
{this.state.show === 'add-repr' && <ActionMenu items={this.addActions} onSelect={this.selectAdd} />}
|
||||
|
||||
@@ -34,12 +34,16 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
|
||||
}
|
||||
|
||||
private copy = async () => {
|
||||
await this.plugin.helpers.viewportScreenshot?.copyToClipboard();
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
message: 'Copied to clipboard.',
|
||||
title: 'Screenshot',
|
||||
timeoutMs: 1500
|
||||
});
|
||||
try {
|
||||
await this.plugin.helpers.viewportScreenshot?.copyToClipboard();
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
message: 'Copied to clipboard.',
|
||||
title: 'Screenshot',
|
||||
timeoutMs: 1500
|
||||
});
|
||||
} catch {
|
||||
return this.copyImg();
|
||||
}
|
||||
}
|
||||
|
||||
private copyImg = async () => {
|
||||
@@ -71,8 +75,7 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
|
||||
<CropControls plugin={this.plugin} />
|
||||
</div>}
|
||||
<div className='msp-flex-row'>
|
||||
{hasClipboardApi && <Button icon={CopySvg} onClick={this.copy} disabled={this.state.isDisabled}>Copy</Button>}
|
||||
{!hasClipboardApi && !this.state.imageData && <Button icon={CopySvg} onClick={this.copyImg} disabled={this.state.isDisabled}>Copy</Button>}
|
||||
{!this.state.imageData && <Button icon={CopySvg} onClick={hasClipboardApi ? this.copy : this.copyImg} disabled={this.state.isDisabled}>Copy</Button>}
|
||||
{this.state.imageData && <Button onClick={() => this.setState({ imageData: void 0 })} disabled={this.state.isDisabled}>Clear</Button>}
|
||||
<Button icon={GetAppSvg} onClick={this.download} disabled={this.state.isDisabled}>Download</Button>
|
||||
</div>
|
||||
|
||||
@@ -184,17 +184,21 @@ export class PluginContext {
|
||||
*/
|
||||
readonly customState: unknown = Object.create(null);
|
||||
|
||||
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {
|
||||
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) {
|
||||
try {
|
||||
this.layout.setRoot(container);
|
||||
if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
|
||||
|
||||
const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false);
|
||||
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
|
||||
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
|
||||
if (canvas3dContext) {
|
||||
(this.canvas3dContext as Canvas3DContext) = canvas3dContext;
|
||||
} else {
|
||||
const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false);
|
||||
const preserveDrawingBuffer = !(this.config.get(PluginConfig.General.DisablePreserveDrawingBuffer) ?? false);
|
||||
const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1;
|
||||
const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
|
||||
const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
|
||||
(this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, enableWboit });
|
||||
}
|
||||
(this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
|
||||
this.canvas3dInit.next(true);
|
||||
let props = this.spec.canvas3d;
|
||||
|
||||
@@ -324,7 +324,7 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
await ctx.update('Converting image...');
|
||||
const blob = await canvasToBlob(this.canvas, 'png');
|
||||
const item = new ClipboardItem({ 'image/png': blob });
|
||||
cb.write([item]);
|
||||
await cb.write([item]);
|
||||
this.plugin.log.message('Image copied to clipboard.');
|
||||
});
|
||||
}
|
||||
|
||||
72
src/mol-script/util/residue-list.ts
Normal file
72
src/mol-script/util/residue-list.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureQuery } from '../../mol-model/structure/query';
|
||||
import { Expression } from '../language/expression';
|
||||
import { MolScriptBuilder as MS } from '../language/builder';
|
||||
import { compile } from '../runtime/query/base';
|
||||
|
||||
// TODO: make this into a separate "language"?
|
||||
|
||||
type ResidueListSelectionEntry =
|
||||
| { kind: 'single', asym_id: string; seq_id: number; ins_code?: string }
|
||||
| { kind: 'range', asym_id: string; seq_id_beg: number; seq_id_end: number; }
|
||||
|
||||
function entriesToQuery(xs: ResidueListSelectionEntry[], kind: 'auth' | 'label') {
|
||||
const groups: Expression[] = [];
|
||||
|
||||
const asym_id_key = kind === 'auth' ? 'auth_asym_id' as const : 'label_asym_id' as const;
|
||||
const seq_id_key = kind === 'auth' ? 'auth_seq_id' as const : 'label_seq_id' as const;
|
||||
|
||||
for (const x of xs) {
|
||||
if (x.kind === 'range') {
|
||||
groups.push(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp(asym_id_key), x.asym_id]),
|
||||
'residue-test': MS.core.rel.inRange([MS.ammp(seq_id_key), x.seq_id_beg, x.seq_id_end])
|
||||
}));
|
||||
} else {
|
||||
const ins_code = (x.ins_code ?? '').trim();
|
||||
|
||||
groups.push(MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.ammp(asym_id_key), x.asym_id]),
|
||||
'residue-test': MS.core.logic.and([
|
||||
MS.core.rel.eq([MS.ammp(seq_id_key), x.seq_id]),
|
||||
MS.core.rel.eq([MS.ammp('pdbx_PDB_ins_code'), ins_code])
|
||||
])
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const query = MS.struct.combinator.merge(groups);
|
||||
|
||||
return compile(query) as StructureQuery;
|
||||
}
|
||||
|
||||
function parseRange(c: string, s: string[], e: number): ResidueListSelectionEntry | undefined {
|
||||
if (!c || s.length === 0 || Number.isNaN(+s[0])) return;
|
||||
if (Number.isNaN(e)) {
|
||||
return { kind: 'single', asym_id: c, seq_id: +s[0], ins_code: s[1] };
|
||||
}
|
||||
return { kind: 'range', asym_id: c, seq_id_beg: +s[0], seq_id_end: e };
|
||||
}
|
||||
|
||||
function parseInsCode(e?: string) {
|
||||
if (!e) return [];
|
||||
return e.split(':');
|
||||
}
|
||||
|
||||
function parseResidueListSelection(input: string): ResidueListSelectionEntry[] {
|
||||
return input.split(',') // A 1-3, B 3 => [A 1-3, B 3]
|
||||
.map(e => e.trim().split(/\s+|[-]/g).filter(e => !!e)) // [A 1-3, B 3] => [[A, 1, 3], [B, 3]]
|
||||
.map(e => parseRange(e[0], parseInsCode(e[1]), +e[2]))
|
||||
.filter(e => !!e) as ResidueListSelectionEntry[];
|
||||
}
|
||||
|
||||
// parses a list of residue ranges, e.g. A 10-100, B 30, C 12:i
|
||||
export function compileResidueListSelection(input: string, idType: 'auth' | 'label') {
|
||||
const entries = parseResidueListSelection(input);
|
||||
return entriesToQuery(entries, idType);
|
||||
}
|
||||
@@ -105,7 +105,12 @@ async function init() {
|
||||
|
||||
const mcBoundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
|
||||
const mcIsosurface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, mcBoundingSphere);
|
||||
const mcIsoSurfaceProps = { doubleSided: true, flatShaded: true, alpha: 1.0 };
|
||||
const mcIsoSurfaceProps = {
|
||||
...PD.getDefaultValues(TextureMesh.Params),
|
||||
doubleSided: true,
|
||||
flatShaded: true,
|
||||
alpha: 1.0
|
||||
};
|
||||
const mcIsoSurfaceValues = TextureMesh.Utils.createValuesSimple(mcIsosurface, mcIsoSurfaceProps, Color(0x112299), 1);
|
||||
// console.log('mcIsoSurfaceValues', mcIsoSurfaceValues)
|
||||
const mcIsoSurfaceState = TextureMesh.Utils.createRenderableState(mcIsoSurfaceProps);
|
||||
@@ -133,7 +138,12 @@ async function init() {
|
||||
console.timeEnd('cpu mc');
|
||||
console.log('surface', surface);
|
||||
Mesh.transform(surface, densityData.transform);
|
||||
const meshProps = { doubleSided: true, flatShaded: false, alpha: 1.0 };
|
||||
const meshProps = {
|
||||
...PD.getDefaultValues(Mesh.Params),
|
||||
doubleSided: true,
|
||||
flatShaded: false,
|
||||
alpha: 1.0
|
||||
};
|
||||
const meshValues = Mesh.Utils.createValuesSimple(surface, meshProps, Color(0x995511), 1);
|
||||
const meshState = Mesh.Utils.createRenderableState(meshProps);
|
||||
const meshRenderObject = createRenderObject('mesh', meshValues, meshState, -1);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Lines } from '../../mol-geo/geometry/lines/lines';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { createRenderObject } from '../../mol-gl/render-object';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
|
||||
const parent = document.getElementById('app')!;
|
||||
parent.style.width = '100%';
|
||||
@@ -33,8 +34,9 @@ function linesRepr() {
|
||||
linesBuilder.addCage(t, dodecahedronCage, 0);
|
||||
const lines = linesBuilder.getLines();
|
||||
|
||||
const values = Lines.Utils.createValuesSimple(lines, {}, Color(0xFF0000), 3);
|
||||
const state = Lines.Utils.createRenderableState({});
|
||||
const props = ParamDefinition.getDefaultValues(Lines.Utils.Params);
|
||||
const values = Lines.Utils.createValuesSimple(lines, props, Color(0xFF0000), 3);
|
||||
const state = Lines.Utils.createRenderableState(props);
|
||||
const renderObject = createRenderObject('lines', values, state, -1);
|
||||
const repr = Representation.fromRenderObject('cage-lines', renderObject);
|
||||
return repr;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -15,6 +15,8 @@ import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { createRenderObject } from '../../mol-gl/render-object';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { Torus } from '../../mol-geo/primitive/torus';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
|
||||
const parent = document.getElementById('app')!;
|
||||
parent.style.width = '100%';
|
||||
@@ -31,17 +33,24 @@ function meshRepr() {
|
||||
const builderState = MeshBuilder.createState();
|
||||
|
||||
const t = Mat4.identity();
|
||||
MeshBuilder.addCage(builderState, t, HexagonalPrismCage(), 0.005, 2, 20);
|
||||
Mat4.scaleUniformly(t, t, 10);
|
||||
MeshBuilder.addCage(builderState, t, HexagonalPrismCage(), 0.05, 2, 20);
|
||||
|
||||
const t2 = Mat4.identity();
|
||||
Mat4.scaleUniformly(t2, t2, 0.1);
|
||||
Mat4.scaleUniformly(t2, t2, 1);
|
||||
MeshBuilder.addPrimitive(builderState, t2, SpikedBall(3));
|
||||
|
||||
const t3 = Mat4.identity();
|
||||
Mat4.scaleUniformly(t3, t3, 8);
|
||||
MeshBuilder.addPrimitive(builderState, t3, Torus({ tubularSegments: 64, radialSegments: 32, tube: 0.1 }));
|
||||
|
||||
const mesh = MeshBuilder.getMesh(builderState);
|
||||
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, {}, Color(0xFF0000), 1);
|
||||
const state = Mesh.Utils.createRenderableState({});
|
||||
const props = ParamDefinition.getDefaultValues(Mesh.Utils.Params);
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, props, Color(0xFF4433), 1);
|
||||
const state = Mesh.Utils.createRenderableState(props);
|
||||
const renderObject = createRenderObject('mesh', values, state, -1);
|
||||
console.log('mesh', renderObject);
|
||||
const repr = Representation.fromRenderObject('mesh', renderObject);
|
||||
return repr;
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ const repr = ShapeRepresentation(getShape, Mesh.Utils);
|
||||
export async function init() {
|
||||
// Create shape from myData and add to canvas3d
|
||||
await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)));
|
||||
console.log('shape', repr);
|
||||
canvas3d.add(repr);
|
||||
canvas3d.requestCameraReset();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { createRenderObject } from '../../mol-gl/render-object';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
|
||||
const parent = document.getElementById('app')!;
|
||||
parent.style.width = '100%';
|
||||
@@ -31,8 +32,9 @@ function spheresRepr() {
|
||||
spheresBuilder.add(-4, 1, 0, 0);
|
||||
const spheres = spheresBuilder.getSpheres();
|
||||
|
||||
const props = ParamDefinition.getDefaultValues(Spheres.Utils.Params);
|
||||
const values = Spheres.Utils.createValuesSimple(spheres, {}, Color(0xFF0000), 1);
|
||||
const state = Spheres.Utils.createRenderableState({});
|
||||
const state = Spheres.Utils.createRenderableState(props);
|
||||
const renderObject = createRenderObject('spheres', values, state, -1);
|
||||
console.log(renderObject);
|
||||
const repr = Representation.fromRenderObject('spheres', renderObject);
|
||||
|
||||
@@ -8,7 +8,7 @@ import './index.html';
|
||||
import { Canvas3D, Canvas3DContext } from '../../mol-canvas3d/canvas3d';
|
||||
import { TextBuilder } from '../../mol-geo/geometry/text/text-builder';
|
||||
import { Text } from '../../mol-geo/geometry/text/text';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ParamDefinition, ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
|
||||
@@ -64,8 +64,9 @@ function spheresRepr() {
|
||||
spheresBuilder.add(-4, 1, 0, 0);
|
||||
const spheres = spheresBuilder.getSpheres();
|
||||
|
||||
const values = Spheres.Utils.createValuesSimple(spheres, {}, Color(0xFF0000), 0.2);
|
||||
const state = Spheres.Utils.createRenderableState({});
|
||||
const props = ParamDefinition.getDefaultValues(Spheres.Utils.Params);
|
||||
const values = Spheres.Utils.createValuesSimple(spheres, props, Color(0xFF0000), 0.2);
|
||||
const state = Spheres.Utils.createRenderableState(props);
|
||||
const renderObject = createRenderObject('spheres', values, state, -1);
|
||||
console.log('spheres', renderObject);
|
||||
const repr = Representation.fromRenderObject('spheres', renderObject);
|
||||
|
||||
Reference in New Issue
Block a user