|
@@ -19,22 +19,25 @@ import { TextBuilder } from '../../../mol-geo/geometry/text/text-builder';
|
|
|
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
|
|
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
|
|
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
|
|
-import { radToDeg } from '../../../mol-math/misc';
|
|
|
+import { radToDeg, arcLength } from '../../../mol-math/misc';
|
|
|
import { Circle } from '../../../mol-geo/primitive/circle';
|
|
|
+import { transformPrimitive } from '../../../mol-geo/primitive/primitive';
|
|
|
|
|
|
export interface AngleData {
|
|
|
- triplets: { lociA: Loci, lociB: Loci, lociC: Loci }[]
|
|
|
+ triples: Loci.Triple[]
|
|
|
}
|
|
|
|
|
|
const SharedParams = {
|
|
|
- color: PD.Color(ColorNames.darkgreen),
|
|
|
+ color: PD.Color(ColorNames.lightgreen),
|
|
|
+ arcScale: PD.Numeric(0.7, { min: 0.01, max: 1, step: 0.01 })
|
|
|
}
|
|
|
|
|
|
const LinesParams = {
|
|
|
...Lines.Params,
|
|
|
...SharedParams,
|
|
|
lineSizeAttenuation: PD.Boolean(true),
|
|
|
- linesSize: PD.Numeric(0.05, { min: 0.01, max: 5, step: 0.01 }),
|
|
|
+ linesSize: PD.Numeric(0.04, { min: 0.01, max: 5, step: 0.01 }),
|
|
|
+ dashLength: PD.Numeric(0.04, { min: 0.01, max: 0.2, step: 0.01 }),
|
|
|
}
|
|
|
|
|
|
const VectorsParams = {
|
|
@@ -50,22 +53,23 @@ type ArcParams = typeof ArcParams
|
|
|
const SectorParams = {
|
|
|
...Mesh.Params,
|
|
|
...SharedParams,
|
|
|
+ sectorOpacity: PD.Numeric(0.75, { min: 0, max: 1, step: 0.01 }),
|
|
|
}
|
|
|
type SectorParams = typeof SectorParams
|
|
|
|
|
|
const TextParams = {
|
|
|
...Text.Params,
|
|
|
- borderWidth: PD.Numeric(0.25, { min: 0, max: 0.5, step: 0.01 }),
|
|
|
+ borderWidth: PD.Numeric(0.2, { min: 0, max: 0.5, step: 0.01 }),
|
|
|
textColor: PD.Color(ColorNames.black),
|
|
|
- textSize: PD.Numeric(0.8, { min: 0.1, max: 5, step: 0.1 }),
|
|
|
+ textSize: PD.Numeric(0.4, { min: 0.1, max: 5, step: 0.1 }),
|
|
|
}
|
|
|
type TextParams = typeof TextParams
|
|
|
|
|
|
const AngleVisuals = {
|
|
|
- 'vectors': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, VectorsParams>) => ShapeRepresentation(getVectorsShape, Lines.Utils),
|
|
|
- 'arc': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, ArcParams>) => ShapeRepresentation(getArcShape, Lines.Utils),
|
|
|
- 'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils),
|
|
|
- 'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils),
|
|
|
+ 'vectors': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, VectorsParams>) => ShapeRepresentation(getVectorsShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
|
|
+ 'arc': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, ArcParams>) => ShapeRepresentation(getArcShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
|
|
+ 'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }) }),
|
|
|
+ 'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<AngleData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
|
|
|
}
|
|
|
type AngleVisualName = keyof typeof AngleVisuals
|
|
|
const AngleVisualOptions = Object.keys(AngleVisuals).map(name => [name, stringToWords(name)] as [AngleVisualName, string])
|
|
@@ -75,33 +79,82 @@ export const AngleParams = {
|
|
|
...ArcParams,
|
|
|
...SectorParams,
|
|
|
...TextParams,
|
|
|
- visuals: PD.MultiSelect<AngleVisualName>(['vectors', 'arc', 'sector', 'text'], AngleVisualOptions),
|
|
|
+ visuals: PD.MultiSelect<AngleVisualName>(['vectors', 'sector', 'text'], AngleVisualOptions),
|
|
|
}
|
|
|
export type AngleParams = typeof AngleParams
|
|
|
export type AngleProps = PD.Values<AngleParams>
|
|
|
|
|
|
//
|
|
|
|
|
|
-const tmpVecA = Vec3()
|
|
|
-const tmpVecB = Vec3()
|
|
|
-const tmpVecC = Vec3()
|
|
|
-// const tmpVecD = Vec3()
|
|
|
+function getAngleState() {
|
|
|
+ return {
|
|
|
+ pointA: Vec3(),
|
|
|
+ pointB: Vec3(),
|
|
|
+ pointC: Vec3(),
|
|
|
|
|
|
-const tmpDirA = Vec3()
|
|
|
-const tmpDirB = Vec3()
|
|
|
-const tmpCenter = Vec3()
|
|
|
+ arcDirA: Vec3(),
|
|
|
+ arcDirC: Vec3(),
|
|
|
+ arcNormal: Vec3(),
|
|
|
+
|
|
|
+ radius: 0,
|
|
|
+ angle: 0,
|
|
|
+ }
|
|
|
+}
|
|
|
+type AngleState = ReturnType<typeof getAngleState>
|
|
|
+
|
|
|
+const tmpVec = Vec3()
|
|
|
+const tmpMat = Mat4()
|
|
|
+
|
|
|
+function setAngleState(triple: Loci.Triple, state: AngleState, arcScale: number) {
|
|
|
+ const { pointA, pointB, pointC } = state
|
|
|
+ const { arcDirA, arcDirC, arcNormal } = state
|
|
|
+
|
|
|
+ const { lociA, lociB, lociC } = triple
|
|
|
+ Loci.getCenter(lociA, pointA)
|
|
|
+ Loci.getCenter(lociB, pointB)
|
|
|
+ Loci.getCenter(lociC, pointC)
|
|
|
+
|
|
|
+ Vec3.sub(arcDirA, pointA, pointB)
|
|
|
+ Vec3.sub(arcDirC, pointC, pointB)
|
|
|
+ Vec3.cross(arcNormal, arcDirA, arcDirC)
|
|
|
+
|
|
|
+ const len = Math.min(Vec3.magnitude(arcDirA), Vec3.magnitude(arcDirC))
|
|
|
+ const radius = len * arcScale
|
|
|
+
|
|
|
+ state.radius = radius
|
|
|
+ state.angle = Vec3.angle(arcDirA, arcDirC)
|
|
|
+
|
|
|
+ return state
|
|
|
+}
|
|
|
+
|
|
|
+function getCircle(state: AngleState, segmentLength?: number) {
|
|
|
+ const { radius, angle } = state
|
|
|
+ const segments = segmentLength ? arcLength(angle, radius) / segmentLength : 32
|
|
|
+
|
|
|
+ Mat4.targetTo(tmpMat, state.pointB, state.pointA, state.arcNormal)
|
|
|
+ Mat4.setTranslation(tmpMat, state.pointB)
|
|
|
+ Mat4.mul(tmpMat, tmpMat, Mat4.rotY180)
|
|
|
+
|
|
|
+ const circle = Circle({ radius, thetaLength: angle, segments })
|
|
|
+ return transformPrimitive(circle, tmpMat)
|
|
|
+}
|
|
|
+
|
|
|
+const tmpState = getAngleState()
|
|
|
+
|
|
|
+function angleLabel(triple: Loci.Triple, arcScale: number) {
|
|
|
+ setAngleState(triple, tmpState, arcScale)
|
|
|
+ const angle = radToDeg(tmpState.angle).toFixed(2)
|
|
|
+ return `Angle ${angle}\u00B0`
|
|
|
+}
|
|
|
|
|
|
//
|
|
|
|
|
|
function buildVectorsLines(data: AngleData, props: AngleProps, lines?: Lines): Lines {
|
|
|
const builder = LinesBuilder.create(128, 64, lines)
|
|
|
- for (let i = 0, il = data.triplets.length; i < il; ++i) {
|
|
|
- const { lociA, lociB, lociC } = data.triplets[i]
|
|
|
- Loci.getCenter(lociA, tmpVecA)
|
|
|
- Loci.getCenter(lociB, tmpVecB)
|
|
|
- Loci.getCenter(lociC, tmpVecC)
|
|
|
- builder.addFixedLengthDashes(tmpVecA, tmpVecB, 0.1, i)
|
|
|
- builder.addFixedLengthDashes(tmpVecB, tmpVecC, 0.1, i)
|
|
|
+ for (let i = 0, il = data.triples.length; i < il; ++i) {
|
|
|
+ setAngleState(data.triples[i], tmpState, props.arcScale)
|
|
|
+ builder.addFixedLengthDashes(tmpState.pointB, tmpState.pointA, props.dashLength, i)
|
|
|
+ builder.addFixedLengthDashes(tmpState.pointB, tmpState.pointC, props.dashLength, i)
|
|
|
}
|
|
|
return builder.getLines()
|
|
|
}
|
|
@@ -109,7 +162,7 @@ function buildVectorsLines(data: AngleData, props: AngleProps, lines?: Lines): L
|
|
|
function getVectorsShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Lines>) {
|
|
|
const lines = buildVectorsLines(data, props, shape && shape.geometry);
|
|
|
const getLabel = function (groupId: number ) {
|
|
|
- return 'Angle Vectors'
|
|
|
+ return angleLabel(data.triples[groupId], props.arcScale)
|
|
|
}
|
|
|
return Shape.create('Angle Vectors', data, lines, () => props.color, () => props.linesSize, getLabel)
|
|
|
}
|
|
@@ -118,14 +171,30 @@ function getVectorsShape(ctx: RuntimeContext, data: AngleData, props: AngleProps
|
|
|
|
|
|
function buildArcLines(data: AngleData, props: AngleProps, lines?: Lines): Lines {
|
|
|
const builder = LinesBuilder.create(128, 64, lines)
|
|
|
-
|
|
|
+ for (let i = 0, il = data.triples.length; i < il; ++i) {
|
|
|
+ setAngleState(data.triples[i], tmpState, props.arcScale)
|
|
|
+ const circle = getCircle(tmpState, props.dashLength)
|
|
|
+ const { indices, vertices } = circle
|
|
|
+ for (let j = 0, jl = indices.length; j < jl; j += 3) {
|
|
|
+ if (j % 2 === 1) continue // draw every other segment to get dashes
|
|
|
+ const start = indices[j] * 3
|
|
|
+ const end = indices[j + 1] * 3
|
|
|
+ const startX = vertices[start]
|
|
|
+ const startY = vertices[start + 1]
|
|
|
+ const startZ = vertices[start + 2]
|
|
|
+ const endX = vertices[end]
|
|
|
+ const endY = vertices[end + 1]
|
|
|
+ const endZ = vertices[end + 2]
|
|
|
+ builder.add(startX, startY, startZ, endX, endY, endZ, i)
|
|
|
+ }
|
|
|
+ }
|
|
|
return builder.getLines()
|
|
|
}
|
|
|
|
|
|
function getArcShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Lines>) {
|
|
|
const lines = buildArcLines(data, props, shape && shape.geometry);
|
|
|
const getLabel = function (groupId: number ) {
|
|
|
- return 'Angle Arc'
|
|
|
+ return angleLabel(data.triples[groupId], props.arcScale)
|
|
|
}
|
|
|
return Shape.create('Angle Arc', data, lines, () => props.color, () => props.linesSize, getLabel)
|
|
|
}
|
|
@@ -134,52 +203,19 @@ function getArcShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, sh
|
|
|
|
|
|
function buildSectorMesh(data: AngleData, props: AngleProps, mesh?: Mesh): Mesh {
|
|
|
const state = MeshBuilder.createState(128, 64, mesh)
|
|
|
- const m = Mat4()
|
|
|
- const tmpVec = Vec3()
|
|
|
-
|
|
|
- for (let i = 0, il = data.triplets.length; i < il; ++i) {
|
|
|
- const { lociA, lociB, lociC } = data.triplets[i]
|
|
|
- console.log(data.triplets[i])
|
|
|
- Loci.getCenter(lociA, tmpVecA)
|
|
|
- Loci.getCenter(lociB, tmpVecB)
|
|
|
- Loci.getCenter(lociC, tmpVecC)
|
|
|
- Vec3.sub(tmpDirA, tmpVecA, tmpVecB)
|
|
|
- Vec3.sub(tmpDirB, tmpVecC, tmpVecB)
|
|
|
-
|
|
|
- const lenA = Vec3.magnitude(tmpDirA)
|
|
|
- const lenB = Vec3.magnitude(tmpDirB)
|
|
|
- let dir: Vec3, len: number
|
|
|
- if (lenA <= lenB) {
|
|
|
- dir = tmpDirA
|
|
|
- len = lenA
|
|
|
- } else {
|
|
|
- dir = tmpDirB
|
|
|
- len = lenB
|
|
|
- }
|
|
|
-
|
|
|
- const center = tmpVecB
|
|
|
- const dirMajor = Vec3.cross(Vec3(), tmpDirA, tmpDirB)
|
|
|
- const dirMinor = dir
|
|
|
-
|
|
|
- Vec3.add(tmpVec, center, dirMajor)
|
|
|
- Mat4.targetTo(m, center, tmpVec, dirMinor)
|
|
|
- Mat4.setTranslation(m, center)
|
|
|
-
|
|
|
- const angle = Vec3.angle(tmpDirA, tmpDirB)
|
|
|
- const circle = Circle({ radius: len, thetaLength: angle })
|
|
|
- console.log(circle)
|
|
|
-
|
|
|
- MeshBuilder.addPrimitive(state, m, circle)
|
|
|
- MeshBuilder.addPrimitiveFlipped(state, m, circle)
|
|
|
+ for (let i = 0, il = data.triples.length; i < il; ++i) {
|
|
|
+ setAngleState(data.triples[i], tmpState, props.arcScale)
|
|
|
+ const circle = getCircle(tmpState)
|
|
|
+ MeshBuilder.addPrimitive(state, Mat4.id, circle)
|
|
|
+ MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle)
|
|
|
}
|
|
|
-
|
|
|
return MeshBuilder.getMesh(state)
|
|
|
}
|
|
|
|
|
|
function getSectorShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Mesh>) {
|
|
|
const mesh = buildSectorMesh(data, props, shape && shape.geometry);
|
|
|
const getLabel = function (groupId: number ) {
|
|
|
- return 'Angle Sector'
|
|
|
+ return angleLabel(data.triples[groupId], props.arcScale)
|
|
|
}
|
|
|
return Shape.create('Angle Sector', data, mesh, () => props.color, () => 1, getLabel)
|
|
|
}
|
|
@@ -188,18 +224,16 @@ function getSectorShape(ctx: RuntimeContext, data: AngleData, props: AngleProps,
|
|
|
|
|
|
function buildText(data: AngleData, props: AngleProps, text?: Text): Text {
|
|
|
const builder = TextBuilder.create(props, 128, 64, text)
|
|
|
- for (let i = 0, il = data.triplets.length; i < il; ++i) {
|
|
|
- const { lociA, lociB, lociC } = data.triplets[i]
|
|
|
- Loci.getCenter(lociA, tmpVecA)
|
|
|
- Loci.getCenter(lociB, tmpVecB)
|
|
|
- Loci.getCenter(lociC, tmpVecC)
|
|
|
- Vec3.sub(tmpDirA, tmpVecB, tmpVecA)
|
|
|
- Vec3.sub(tmpDirB, tmpVecC, tmpVecB)
|
|
|
- Vec3.add(tmpCenter, tmpVecA, tmpVecC)
|
|
|
- Vec3.scale(tmpCenter, tmpCenter, 0.5)
|
|
|
- const angle = radToDeg(Vec3.angle(tmpDirA, tmpDirB)).toPrecision(2)
|
|
|
+ for (let i = 0, il = data.triples.length; i < il; ++i) {
|
|
|
+ setAngleState(data.triples[i], tmpState, props.arcScale)
|
|
|
+
|
|
|
+ Vec3.add(tmpVec, tmpState.arcDirA, tmpState.arcDirC)
|
|
|
+ Vec3.setMagnitude(tmpVec, tmpVec, tmpState.radius)
|
|
|
+ Vec3.add(tmpVec, tmpState.pointB, tmpVec)
|
|
|
+
|
|
|
+ const angle = radToDeg(tmpState.angle).toFixed(2)
|
|
|
const label = `${angle}\u00B0`
|
|
|
- builder.add(label, tmpCenter[0], tmpCenter[1], tmpCenter[2], 0.1, i)
|
|
|
+ builder.add(label, tmpVec[0], tmpVec[1], tmpVec[2], 0.1, i)
|
|
|
}
|
|
|
return builder.getText()
|
|
|
}
|
|
@@ -207,7 +241,7 @@ function buildText(data: AngleData, props: AngleProps, text?: Text): Text {
|
|
|
function getTextShape(ctx: RuntimeContext, data: AngleData, props: AngleProps, shape?: Shape<Text>) {
|
|
|
const text = buildText(data, props, shape && shape.geometry);
|
|
|
const getLabel = function (groupId: number ) {
|
|
|
- return 'Angle Text'
|
|
|
+ return angleLabel(data.triples[groupId], props.arcScale)
|
|
|
}
|
|
|
return Shape.create('Angle Text', data, text, () => props.textColor, () => props.textSize, getLabel)
|
|
|
}
|