diff --git a/package-lock.json b/package-lock.json index eef02eb8afb9f6be4baa39aacd2c15fb8fc6b413..25dccbec517c83159a708ec29ec305d1a34fc3ef 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts index 4da00cdbf9eb242a0913d7b8adbda6b922f53929..862b01b07cb334e986aa927e78d00197ecb2618f 100644 --- a/src/apps/canvas/app.ts +++ b/src/apps/canvas/app.ts @@ -5,21 +5,22 @@ */ import Viewer from 'mol-view/viewer'; -import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl } from './util'; +import { getCifFromUrl, getModelsFromMmcif, getCifFromFile, getCcp4FromUrl, getVolumeFromCcp4, getCcp4FromFile } from './util'; import { StructureView } from './structure-view'; import { BehaviorSubject } from 'rxjs'; import { CifBlock } from 'mol-io/reader/cif'; -import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4'; -import { VolumeRepresentation } from 'mol-geo/representation/volume'; -import IsosurfaceVisual from 'mol-geo/representation/volume/isosurface'; +import { VolumeView } from './volume-view'; +import { Ccp4File } from 'mol-io/reader/ccp4/schema'; export class App { viewer: Viewer container: HTMLDivElement | null = null; canvas: HTMLCanvasElement | null = null; structureView: StructureView | null = null; + volumeView: VolumeView | null = null; - pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null) + structureLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null) + volumeLoaded: BehaviorSubject<VolumeView | null> = new BehaviorSubject<VolumeView | null>(null) initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) { this.canvas = _canvas @@ -63,7 +64,7 @@ export class App { async loadMmcif(cif: CifBlock, assemblyId?: string) { const models = await this.runTask(getModelsFromMmcif(cif), 'Build models') this.structureView = await this.runTask(StructureView(this, this.viewer, models, { assemblyId }), 'Init structure view') - this.pdbIdLoaded.next(this.structureView) + this.structureLoaded.next(this.structureView) } async loadPdbIdOrMmcifUrl(idOrUrl: string, options?: { assemblyId?: string, binary?: boolean }) { @@ -82,17 +83,21 @@ export class App { // - async loadCcp4File() { - const url = 'http://localhost:8091/ngl/data/betaGal.mrc' - const ccp4 = await getCcp4FromUrl(url) - console.log(ccp4) - const volume = await volumeFromCcp4(ccp4).run() - const volRepr = VolumeRepresentation(IsosurfaceVisual) - await volRepr.createOrUpdate({ - isoValue: 1 - }, volume).run() - this.viewer.add(volRepr) - console.log('volRepr', volRepr) - this.viewer.requestDraw(true) + async loadCcp4(ccp4: Ccp4File) { + const volume = await this.runTask(getVolumeFromCcp4(ccp4), 'Get Volume') + this.volumeView = await this.runTask(VolumeView(this, this.viewer, volume), 'Init volume view') + this.volumeLoaded.next(this.volumeView) + } + + async loadCcp4File(file: File) { + if (this.volumeView) this.volumeView.destroy(); + const ccp4 = await this.runTask(getCcp4FromFile(file), 'Load CCP4 from file') + this.loadCcp4(ccp4) + } + + async loadCcp4Url(url: string) { + if (this.volumeView) this.volumeView.destroy(); + const ccp4 = await this.runTask(getCcp4FromUrl(url), 'Load CCP4 from URL') + this.loadCcp4(ccp4) } } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx index bc15e244bee3d02e4e7d226965cbb1d3513a274f..7f2d390928f589244c393de613d88f7209456de3 100644 --- a/src/apps/canvas/component/app.tsx +++ b/src/apps/canvas/component/app.tsx @@ -10,6 +10,8 @@ import { App } from '../app'; import { Viewport } from './viewport'; import { StructureViewComponent } from './structure-view'; import { Examples } from '../examples'; +import { VolumeViewComponent } from './volume-view'; +import { VolumeView } from '../volume-view'; export interface AppProps { app: App @@ -17,25 +19,28 @@ export interface AppProps { export interface AppState { structureView: StructureView | null, + volumeView: VolumeView | null, binary: boolean } export class AppComponent extends React.Component<AppProps, AppState> { state = { structureView: this.props.app.structureView, + volumeView: this.props.app.volumeView, binary: false } componentDidMount() { - this.props.app.pdbIdLoaded.subscribe((structureView) => { - this.setState({ - structureView: this.props.app.structureView - }) + this.props.app.structureLoaded.subscribe((structureView) => { + this.setState({ structureView: this.props.app.structureView }) + }) + this.props.app.volumeLoaded.subscribe((volumeView) => { + this.setState({ volumeView: this.props.app.volumeView }) }) } render() { - const { structureView } = this.state + const { structureView, volumeView } = this.state return <div style={{width: '100%', height: '100%'}}> <div style={{left: '0px', right: '350px', height: '100%', position: 'absolute'}}> @@ -69,6 +74,16 @@ export class AppComponent extends React.Component<AppProps, AppState> { }} /> </div> + <div> + <span>Load CCP4/MRC file </span> + <input + accept='*.ccp4,*.mrc, *.map' + type='file' + onChange={e => { + if (e.target.files) this.props.app.loadCcp4File(e.target.files[0]) + }} + /> + </div> <div> <span>Load example </span> <select @@ -87,6 +102,10 @@ export class AppComponent extends React.Component<AppProps, AppState> { <div style={{marginBottom: '10px'}}> {structureView ? <StructureViewComponent structureView={structureView} /> : ''} </div> + <hr/> + <div style={{marginBottom: '10px'}}> + {volumeView ? <VolumeViewComponent volumeView={volumeView} /> : ''} + </div> </div> </div>; } diff --git a/src/apps/canvas/component/structure-view.tsx b/src/apps/canvas/component/structure-view.tsx index 91bbe6cc93365ccf4ab00706fcbfa016ee148205..9518d047904f6f4e200dd3e57988826e81b3b8b8 100644 --- a/src/apps/canvas/component/structure-view.tsx +++ b/src/apps/canvas/component/structure-view.tsx @@ -10,17 +10,6 @@ import { StructureRepresentation } from 'mol-geo/representation/structure'; import { RepresentationComponent } from './representation'; import { Representation } from 'mol-geo/representation'; -// export function FileInput (props: { -// accept: string -// onChange: (v: FileList | null) => void, -// }) { -// return <input -// accept={props.accept || '*.*'} -// type='file' -// onChange={e => props.onChange.call(null, e.target.files)} -// /> -// } - export interface StructureViewComponentProps { structureView: StructureView } diff --git a/src/apps/canvas/component/volume-view.tsx b/src/apps/canvas/component/volume-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f816102bbf72f8599e885a1ef1d12c4041e7ff20 --- /dev/null +++ b/src/apps/canvas/component/volume-view.tsx @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { RepresentationComponent } from './representation'; +import { Representation } from 'mol-geo/representation'; +import { VolumeView } from '../volume-view'; +import { VolumeRepresentation } from 'mol-geo/representation/volume'; + +export interface VolumeViewComponentProps { + volumeView: VolumeView +} + +export interface VolumeViewComponentState { + volumeView: VolumeView + label: string + active: { [k: string]: boolean } + volumeRepresentations: { [k: string]: VolumeRepresentation<any> } +} + +export class VolumeViewComponent extends React.Component<VolumeViewComponentProps, VolumeViewComponentState> { + state = this.stateFromVolumeView(this.props.volumeView) + + private stateFromVolumeView(vv: VolumeView) { + return { + volumeView: vv, + label: vv.label, + active: vv.active, + volumeRepresentations: vv.volumeRepresentations + } + } + + componentWillMount() { + this.setState(this.stateFromVolumeView(this.props.volumeView)) + } + + componentDidMount() { + const vv = this.props.volumeView + + this.props.volumeView.updated.subscribe(() => this.setState({ + volumeRepresentations: vv.volumeRepresentations + })) + } + + componentWillReceiveProps(nextProps: VolumeViewComponentProps) { + if (nextProps.volumeView !== this.props.volumeView) { + this.setState(this.stateFromVolumeView(nextProps.volumeView)) + + nextProps.volumeView.updated.subscribe(() => this.setState({ + volumeRepresentations: nextProps.volumeView.volumeRepresentations + })) + } + } + + // async update(state: Partial<VolumeViewComponentState>) { + // const vv = this.state.volumeView + // this.setState(this.stateFromVolumeView(vv)) + // } + + render() { + const { volumeView, label, active, volumeRepresentations } = this.state + + return <div> + <div> + <h2>{label}</h2> + </div> + <div> + <div> + <h4>Active</h4> + { Object.keys(active).map((k, i) => { + return <div key={i}> + <input + type='checkbox' + checked={active[k]} + onChange={(e) => { + volumeView.setVolumeRepresentation(k, e.target.checked) + }} + /> {k} + </div> + } ) } + </div> + <div> + <h3>Volume Representations</h3> + { Object.keys(volumeRepresentations).map((k, i) => { + if (active[k]) { + return <div key={i}> + <RepresentationComponent + repr={volumeRepresentations[k] as Representation<any>} + viewer={volumeView.viewer} + app={volumeView.app} + /> + </div> + } else { + return '' + } + } ) } + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts index 226785421fafbb0e93aa98f1a58cccd0e41534a8..dcbc58697bd99d9897066ba0cd1c120bb9c6b27d 100644 --- a/src/apps/canvas/util.ts +++ b/src/apps/canvas/util.ts @@ -4,11 +4,13 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { readUrl, readFile, readUrlAsBuffer } from 'mol-util/read'; +import { readUrl, readFile, readUrlAsBuffer, readFileAsBuffer } from 'mol-util/read'; import CIF, { CifBlock } from 'mol-io/reader/cif' import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure'; import CCP4 from 'mol-io/reader/ccp4/parser' import { FileHandle } from 'mol-io/common/file-handle'; +import { Ccp4File } from 'mol-io/reader/ccp4/schema'; +import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4'; // import { parse as parseObj } from 'mol-io/reader/obj/parser' // export async function getObjFromUrl(url: string) { @@ -53,9 +55,17 @@ export async function getCcp4FromUrl(url: string) { return getCcp4FromData(await readUrlAsBuffer(url)) } +export async function getCcp4FromFile(file: File) { + return getCcp4FromData(await readFileAsBuffer(file)) +} + export async function getCcp4FromData(data: Uint8Array) { const file = FileHandle.fromBuffer(data) const parsed = await CCP4(file).run() if (parsed.isError) throw parsed return parsed.result +} + +export async function getVolumeFromCcp4(ccp4: Ccp4File) { + return await volumeFromCcp4(ccp4).run() } \ No newline at end of file diff --git a/src/apps/canvas/volume-view.ts b/src/apps/canvas/volume-view.ts new file mode 100644 index 0000000000000000000000000000000000000000..968fca0727000d3d1245596f9f88dced2d730615 --- /dev/null +++ b/src/apps/canvas/volume-view.ts @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Viewer from 'mol-view/viewer'; +import { BehaviorSubject } from 'rxjs'; +import { App } from './app'; +import { Progress } from 'mol-task'; +import { VolumeData } from 'mol-model/volume'; +import { VolumeRepresentation } from 'mol-geo/representation/volume'; +import IsosurfaceVisual from 'mol-geo/representation/volume/isosurface'; +import { Vec3 } from 'mol-math/linear-algebra'; + +export interface VolumeView { + readonly app: App + readonly viewer: Viewer + + readonly label: string + readonly volume: VolumeData + + readonly active: { [k: string]: boolean } + readonly volumeRepresentations: { [k: string]: VolumeRepresentation<any> } + readonly updated: BehaviorSubject<null> + + setVolumeRepresentation(name: string, value: boolean): void + destroy: () => void +} + +interface StructureViewProps { + assemblyId?: string + symmetryFeatureId?: number +} + +export async function VolumeView(app: App, viewer: Viewer, volume: VolumeData, props: StructureViewProps = {}): Promise<VolumeView> { + const active: { [k: string]: boolean } = { + isosurface: true, + volume: false, + } + + const volumeRepresentations: { [k: string]: VolumeRepresentation<any> } = { + isosurface: VolumeRepresentation(IsosurfaceVisual), + } + + const updated: BehaviorSubject<null> = new BehaviorSubject<null>(null) + + let label: string = 'Volume' + + async function setVolumeRepresentation(k: string, value: boolean) { + active[k] = value + await createVolumeRepr() + } + + async function createVolumeRepr() { + for (const k in volumeRepresentations) { + if (active[k]) { + await app.runTask(volumeRepresentations[k].createOrUpdate({}, volume).run( + progress => console.log(Progress.format(progress)) + ), 'Create/update representation') + viewer.add(volumeRepresentations[k]) + } else { + viewer.remove(volumeRepresentations[k]) + } + } + + // const center = Vec3.clone(volume.cell.size) + // Vec3.scale(center, center, 0.5) + // viewer.center(center) + + updated.next(null) + viewer.requestDraw(true) + console.log('stats', viewer.stats) + } + + await createVolumeRepr() + + return { + app, + viewer, + + get label() { return label }, + volume, + + active, + volumeRepresentations, + setVolumeRepresentation, + updated, + + destroy: () => { + for (const k in volumeRepresentations) { + viewer.remove(volumeRepresentations[k]) + volumeRepresentations[k].destroy() + } + viewer.requestDraw(true) + } + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 5f22df51d937b41ce3b94cc69802090748500146..18e9722ddb304299c7f833b4863173dc7bcbae83 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/src/mol-geo/representation/volume/index.ts @@ -5,7 +5,6 @@ */ import { Task } from 'mol-task' -import { RenderObject } from 'mol-gl/render-object'; import { RepresentationProps, Representation, Visual } from '..'; import { VolumeData } from 'mol-model/volume'; import { PickingId } from '../../geometry/picking'; @@ -13,32 +12,41 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { MarkerAction } from '../../geometry/marker-data'; import { Geometry } from '../../geometry/geometry'; import { paramDefaultValues } from 'mol-view/parameter'; +import { IsosurfaceParams } from './isosurface'; export interface VolumeVisual<P extends RepresentationProps = {}> extends Visual<VolumeData, P> { } export interface VolumeRepresentation<P extends RepresentationProps = {}> extends Representation<VolumeData, P> { } export const VolumeParams = { - ...Geometry.Params + ...Geometry.Params, + ...IsosurfaceParams } export const DefaultVolumeProps = paramDefaultValues(VolumeParams) export type VolumeProps = typeof DefaultVolumeProps export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeData: VolumeData) => VolumeVisual<P>): VolumeRepresentation<P> { - const renderObjects: RenderObject[] = [] - let _volumeData: VolumeData + let visual: VolumeVisual<any> let _props: P + let busy = false function createOrUpdate(props: Partial<P> = {}, volumeData?: VolumeData) { _props = Object.assign({}, DefaultVolumeProps, _props, props) return Task.create('VolumeRepresentation.create', async ctx => { - if (volumeData) { - _volumeData = volumeData - const visual = visualCtor(_volumeData) - await visual.createOrUpdate(ctx, props, _volumeData) - if (visual.renderObject) renderObjects.push(visual.renderObject) + // TODO queue it somehow + if (busy) return + + if (!visual && !volumeData) { + throw new Error('volumeData missing') + } else if (volumeData && !visual) { + busy = true + visual = visualCtor(volumeData) + await visual.createOrUpdate(ctx, props, volumeData) + busy = false } else { - throw new Error('missing volumeData') + busy = true + await visual.createOrUpdate(ctx, props, volumeData) + busy = false } }); } @@ -46,7 +54,9 @@ export function VolumeRepresentation<P extends VolumeProps>(visualCtor: (volumeD return { label: 'Volume mesh', params: VolumeParams, - get renderObjects () { return renderObjects }, + get renderObjects() { + return visual && visual.renderObject ? [ visual.renderObject ] : [] + }, get props () { return _props }, createOrUpdate, getLoci(pickingId: PickingId) { diff --git a/src/mol-geo/representation/volume/isosurface.ts b/src/mol-geo/representation/volume/isosurface.ts index cc472eeaba5514af3ea59384a420a0d6cf8c0697..72be9abcf2ec993c42b52be757523a179dbeb20d 100644 --- a/src/mol-geo/representation/volume/isosurface.ts +++ b/src/mol-geo/representation/volume/isosurface.ts @@ -17,58 +17,91 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { LocationIterator } from '../../util/location-iterator'; import { NullLocation } from 'mol-model/location'; import { createIdentityTransform } from '../../geometry/transform-data'; -import { createRenderableState } from '../../geometry/geometry'; +import { createRenderableState, updateRenderableState } from '../../geometry/geometry'; import { paramDefaultValues, NumberParam } from 'mol-view/parameter'; +import { ValueCell } from 'mol-util'; -export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue) { +export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValue, mesh?: Mesh) { return Task.create<Mesh>('Volume Surface', async ctx => { ctx.update({ message: 'Marching cubes...' }); - const mesh = await computeMarchingCubesMesh({ + const surface = await computeMarchingCubesMesh({ isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue, scalarField: volume.data - }).runAsChild(ctx); + }, mesh).runAsChild(ctx); const transform = VolumeData.getGridToCartesianTransform(volume); ctx.update({ message: 'Transforming mesh...' }); - Mesh.transformImmediate(mesh, transform); + Mesh.transformImmediate(surface, transform); + Mesh.computeNormalsImmediate(surface) - return mesh; + return surface; }); } export const IsosurfaceParams = { ...Mesh.Params, - isoValue: NumberParam('Iso Value', '', 2, -5, 5, 0.01), + isoValue: NumberParam('Iso Value', '', 2, -15, 15, 0.01), } export const DefaultIsosurfaceProps = paramDefaultValues(IsosurfaceParams) export type IsosurfaceProps = typeof DefaultIsosurfaceProps export default function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> { - let renderObject: MeshRenderObject let currentProps = DefaultIsosurfaceProps + let renderObject: MeshRenderObject + let currentVolume: VolumeData + let mesh: Mesh + + async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<IsosurfaceProps> = {}) { + currentProps = { ...DefaultIsosurfaceProps, ...props } + + mesh = await computeVolumeSurface(volume, VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx) + + const locationIt = LocationIterator(1, 1, () => NullLocation) + const transform = createIdentityTransform() + + const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps) + const state = createRenderableState(currentProps) + + renderObject = createMeshRenderObject(values, state) + } + + async function update(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}) { + const newProps = { ...currentProps, ...props } + + let createMesh = false + + if (newProps.isoValue !== currentProps.isoValue) createMesh = true + + if (createMesh) { + mesh = await computeVolumeSurface(currentVolume, VolumeIsoValue.relative(currentVolume.dataStats, currentProps.isoValue), mesh).runAsChild(ctx) + ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) + } + + Mesh.updateValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + currentProps = newProps + return true + } return { get renderObject () { return renderObject }, async createOrUpdate(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}, volume?: VolumeData) { - currentProps = { ...DefaultIsosurfaceProps, ...props } - - console.log('MOINMOIN') - if (!volume) return - - const mesh = await computeVolumeSurface(volume, VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)).runAsChild(ctx) - if (!props.flatShaded) { - Mesh.computeNormalsImmediate(mesh) + if (!volume && !currentVolume) { + throw new Error('missing volume') + } else if (volume && (!currentVolume || !renderObject)) { + currentVolume = volume + await create(ctx, volume, props) + } else if (volume && volume !== currentVolume) { + currentVolume = volume + await create(ctx, volume, props) + } else { + await update(ctx, props) } - const locationIt = LocationIterator(1, 1, () => NullLocation) - const transform = createIdentityTransform() - - const values = await Mesh.createValues(ctx, mesh, transform, locationIt, currentProps) - const state = createRenderableState(currentProps) + currentProps = { ...DefaultIsosurfaceProps, ...props } - renderObject = createMeshRenderObject(values, state) - console.log('renderObject', renderObject) }, getLoci(pickingId: PickingId) { // TODO diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index 442f8e3927a3f2ad3358fabba82a26cbe01096fb..c74acbe2a8f7fdb681880bfa0afe0ac6b63cc3b4 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -176,6 +176,72 @@ namespace Mat4 { return Mat4.copy(Mat4.zero(), a); } + /** + * Returns the translation vector component of a transformation matrix. + */ + export function getTranslation(out: Vec3, mat: Mat4) { + out[0] = mat[12]; + out[1] = mat[13]; + out[2] = mat[14]; + return out; + } + + /** + * Returns the scaling factor component of a transformation matrix. + */ + export function getScaling(out: Vec3, mat: Mat4) { + let m11 = mat[0]; + let m12 = mat[1]; + let m13 = mat[2]; + let m21 = mat[4]; + let m22 = mat[5]; + let m23 = mat[6]; + let m31 = mat[8]; + let m32 = mat[9]; + let m33 = mat[10]; + out[0] = Math.sqrt(m11 * m11 + m12 * m12 + m13 * m13); + out[1] = Math.sqrt(m21 * m21 + m22 * m22 + m23 * m23); + out[2] = Math.sqrt(m31 * m31 + m32 * m32 + m33 * m33); + return out; + } + + /** + * Returns a quaternion representing the rotational component of a transformation matrix. + */ + export function getRotation(out: Quat, mat: Mat4) { + // Algorithm taken from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + let trace = mat[0] + mat[5] + mat[10]; + let S = 0; + + if (trace > 0) { + S = Math.sqrt(trace + 1.0) * 2; + out[3] = 0.25 * S; + out[0] = (mat[6] - mat[9]) / S; + out[1] = (mat[8] - mat[2]) / S; + out[2] = (mat[1] - mat[4]) / S; + } else if ((mat[0] > mat[5]) && (mat[0] > mat[10])) { + S = Math.sqrt(1.0 + mat[0] - mat[5] - mat[10]) * 2; + out[3] = (mat[6] - mat[9]) / S; + out[0] = 0.25 * S; + out[1] = (mat[1] + mat[4]) / S; + out[2] = (mat[8] + mat[2]) / S; + } else if (mat[5] > mat[10]) { + S = Math.sqrt(1.0 + mat[5] - mat[0] - mat[10]) * 2; + out[3] = (mat[8] - mat[2]) / S; + out[0] = (mat[1] + mat[4]) / S; + out[1] = 0.25 * S; + out[2] = (mat[6] + mat[9]) / S; + } else { + S = Math.sqrt(1.0 + mat[10] - mat[0] - mat[5]) * 2; + out[3] = (mat[1] - mat[4]) / S; + out[0] = (mat[8] + mat[2]) / S; + out[1] = (mat[6] + mat[9]) / S; + out[2] = 0.25 * S; + } + + return out; + } + export function transpose(out: Mat4, a: Mat4) { // If we are transposing ourselves we can skip a few steps but have to cache some values if (out === a) {