diff --git a/.gitignore b/.gitignore index d15a641df7119e69a3a736a3480fcac35df8ecec..615776276627c3445513db121e5a050df0bde3c9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules/ debug.log npm-debug.log -*.sublime-workspace \ No newline at end of file +*.sublime-workspace + +web/render-test/index.js \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 00ad71fba1a3d0ed26f404f386e5966e082327cd..55712c19f1dffd66d4fed7a406a354f215c43a14 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b3225a1f2aa3536b48e7f68f44220d42958af6eb..c59f2cf6c7d1f739351734f9e300c144ced9e9c9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,6 +9,11 @@ "problemMatcher": [ "$tsc" ] + }, + { + "type": "npm", + "script": "app-render-test", + "problemMatcher": [] } ] } \ No newline at end of file diff --git a/package.json b/package.json index eae72c243aea5b3228b61eed9eb7e55f69eaf1e9..ac5b086a1c6d81581286824de3a31856c164b584 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,13 @@ }, "scripts": { "lint": "tslint src/**/*.ts", - "build": "tsc", + "build": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ && tsc", "watch": "tsc -watch", + "watch-shader": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ --watch", "test": "jest", - "script": "node build/node_modules/script.js" + "script": "node build/node_modules/script.js", + "app-render-test": "webpack build/node_modules/apps/render-test/index.js --mode development -o web/render-test/index.js", + "app-render-test-watch": "webpack build/node_modules/apps/render-test/index.js -w --mode development -o web/render-test/index.js" }, "jest": { "moduleFileExtensions": [ @@ -30,29 +33,41 @@ "build/node_modules" ], "moduleNameMapper": { - "mol-task($|/.*)": "<rootDir>/src/mol-task$1", - "mol-comp($|/.*)": "<rootDir>/src/mol-comp$1", - "mol-util($|/.*)": "<rootDir>/src/mol-util$1", "mol-data($|/.*)": "<rootDir>/src/mol-data$1", - "mol-math($|/.*)": "<rootDir>/src/mol-math$1", + "mol-gl($|/.*)": "<rootDir>/src/mol-gl$1", "mol-io($|/.*)": "<rootDir>/src/mol-io$1", - "mol-model($|/.*)": "<rootDir>/src/mol-model$1" + "mol-math($|/.*)": "<rootDir>/src/mol-math$1", + "mol-model($|/.*)": "<rootDir>/src/mol-model$1", + "mol-ql($|/.*)": "<rootDir>/src/mol-ql$1", + "mol-task($|/.*)": "<rootDir>/src/mol-task$1", + "mol-util($|/.*)": "<rootDir>/src/mol-util$1" }, "testRegex": "\\.spec\\.ts$" }, + "glslify": { + "transform": [ + "glslify-import" + ] + }, "author": "", "license": "MIT", "devDependencies": { "@types/argparse": "^1.0.33", "@types/benchmark": "^1.0.31", "@types/express": "^4.11.1", - "@types/jest": "^22.2.2", - "@types/node": "^9.6.1", + "@types/jest": "^22.1.3", + "@types/node": "^9.6.0", "@types/node-fetch": "^1.6.7", "@types/react": "^16.1.0", "@types/react-dom": "^16.0.4", "benchmark": "^2.1.4", - "jest": "^22.4.3", + "copyfiles": "^2.0.0", + "cpx": "^1.5.0", + "extra-watch-webpack-plugin": "^1.0.1", + "glslify-import": "^3.1.0", + "glslify-loader": "^1.0.2", + "jest": "^22.4.2", + "raw-loader": "^0.5.1", "regl": "git+https://github.com/regl-project/regl.git#45c6ec570232420fca21567499c9c5a2a054432e", "rollup": "^0.56.5", "rollup-plugin-buble": "^0.19.2", @@ -63,8 +78,10 @@ "ts-jest": "^22.4.2", "tslint": "^5.9.1", "typescript": "^2.8.1", - "uglify-js": "^3.3.16", - "util.promisify": "^1.0.0" + "uglify-js": "^3.3.12", + "util.promisify": "^1.0.0", + "webpack": "^4.2.0", + "webpack-cli": "^2.0.13" }, "dependencies": { "argparse": "^1.0.10", diff --git a/src/apps/render-test/index.tsx b/src/apps/render-test/index.tsx index 2647621e8127891712acd5889ca58517410ebbb1..246502b305987115ab9d1930e4e73ecc42ea8876 100644 --- a/src/apps/render-test/index.tsx +++ b/src/apps/render-test/index.tsx @@ -5,7 +5,9 @@ */ import UI from './ui' +import State from './state' import * as React from 'react' import * as ReactDOM from 'react-dom' -ReactDOM.render(<UI/>, document.getElementById('app')); \ No newline at end of file +const state = new State() +ReactDOM.render(<UI state={state} />, document.getElementById('app')); \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts new file mode 100644 index 0000000000000000000000000000000000000000..982a0284b71e9f41f0606af4fe00d8e287d6ba7a --- /dev/null +++ b/src/apps/render-test/state.ts @@ -0,0 +1,171 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import * as glContext from 'mol-gl/context' +import { Camera } from 'mol-gl/camera' +import { Vec3, Mat4 } from 'mol-math/linear-algebra' +import { PointRenderable, MeshRenderable } from 'mol-gl/renderable' +import Attribute from 'mol-gl/attribute'; +import Model from 'mol-gl/model'; +import { createTransformAttributes } from 'mol-gl/renderable/util'; +import { calculateTextureInfo } from 'mol-gl/util'; +import Icosahedron from 'mol-geo/primitive/icosahedron' +import Box from 'mol-geo/primitive/box' + +export default class State { + regl: REGL.Regl + + initRegl (container: HTMLDivElement) { + const regl = glContext.create({ + container, + extensions: [ + 'OES_texture_float', + 'OES_texture_float_linear', + 'OES_element_index_uint', + // 'ext_disjoint_timer_query', + 'EXT_blend_minmax', + 'ANGLE_instanced_arrays' + ], + // profile: true + }) + + const camera = Camera.create(regl, container, { + center: Vec3.create(0, 0, 0) + }) + + const p1 = Vec3.create(0, 4, 0) + const p2 = Vec3.create(-3, 0, 0) + + const model1 = Model(regl) + const model2 = Model(regl, { position: p1 }) + const model3 = Model(regl, { position: p2 }) + + const position = Attribute.create(regl, new Float32Array([0, -1, 0, -1, 0, 0, 1, 1, 0]), { size: 3 }) + const normal = Attribute.create(regl, new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]), { size: 3 }) + + const transformArray1 = new Float32Array(16) + const transformArray2 = new Float32Array(16 * 3) + const m4 = Mat4.identity() + Mat4.toArray(m4, transformArray1, 0) + Mat4.toArray(m4, transformArray2, 0) + Mat4.setTranslation(m4, p1) + Mat4.toArray(m4, transformArray2, 16) + Mat4.setTranslation(m4, p2) + Mat4.toArray(m4, transformArray2, 32) + + + + const colorTexInfo = calculateTextureInfo(3, 3) + const color = new Uint8Array(colorTexInfo.length) + color.set([ + 0, 0, 255, + 0, 255, 0, + 255, 0, 0 + ]) + console.log(color, colorTexInfo) + const colorTex = regl.texture({ + width: colorTexInfo.width, + height: colorTexInfo.height, + format: 'rgb', + type: 'uint8', + wrapS: 'clamp', + wrapT: 'clamp', + data: color + }) + + // position.update((array: Float32Array) => { + // positionFromModel({}, array, 0) + // }) + + const points = PointRenderable.create(regl, { + position, + ...createTransformAttributes(regl, transformArray1) + }) + const mesh = MeshRenderable.create(regl, + { + position, + normal, + ...createTransformAttributes(regl, transformArray2) + }, + { + colorTex, + colorTexSize: [ colorTexInfo.width, colorTexInfo.height ] + } + ) + + const sphere = Icosahedron(1, 1) + console.log(sphere) + + const box = Box(1, 1, 1, 1, 1, 1) + console.log(box) + + const points2 = PointRenderable.create(regl, { + position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }), + ...createTransformAttributes(regl, transformArray1) + }) + + const mesh2 = MeshRenderable.create(regl, + { + position: Attribute.create(regl, new Float32Array(box.vertices), { size: 3 }), + normal: Attribute.create(regl, new Float32Array(box.normals), { size: 3 }), + ...createTransformAttributes(regl, transformArray2) + }, + { + colorTex, + colorTexSize: [ colorTexInfo.width, colorTexInfo.height ], + 'light.position': Vec3.create(0, 0, -20), + 'light.color': Vec3.create(1.0, 1.0, 1.0), + 'light.ambient': Vec3.create(0.5, 0.5, 0.5), + 'light.falloff': 0, + 'light.radius': 500 + }, + box.indices + ) + + const baseContext = regl({ + context: { + model: Mat4.identity(), + transform: Mat4.setTranslation(Mat4.identity(), Vec3.create(6, 0, 0)) + }, + uniforms: { + model: regl.context('model' as any), + transform: regl.context('transform' as any), + } + }) + + regl.frame((ctx) => { + camera.update((state: any) => { + if (!camera.isDirty()) return + baseContext(() => { + // console.log(ctx) + regl.clear({color: [0, 0, 0, 1]}) + position.update(array => { array[0] = Math.random() }) + // points.update(a => { a.position[0] = Math.random() }) + // mesh.draw() + // points.draw() + mesh2.draw() + points2.draw() + // model1({}, ({ transform }) => { + // points.draw() + // }) + // model2({}, ({ transform }) => { + // points.draw() + // model3({ transform }, () => { + // points.draw() + // }) + // }) + }) + }, undefined) + }) + + this.regl = regl + } + + constructor() { + + } +} diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx index 09f3215c8a36cdb0410af0796eb548381708699b..fd88f2f5f1473ab7ef998391be930a4e89ad6a03 100644 --- a/src/apps/render-test/ui.tsx +++ b/src/apps/render-test/ui.tsx @@ -5,11 +5,18 @@ */ import * as React from 'react' +import State from './state' + +export default class Root extends React.Component<{ state: State }, { }> { + private canvasContainer: HTMLDivElement | null = null; + + componentDidMount() { + if (this.canvasContainer) this.props.state.initRegl(this.canvasContainer) + } -export default class Root extends React.Component { render() { - return <div style={{ position: 'absolute', top: 0, right: 0, left: 0, bottom: 0, overflow: 'hidden' }}> - + return <div ref={elm => this.canvasContainer = elm} style={{ position: 'absolute', top: 0, right: 0, left: 0, bottom: 0, overflow: 'hidden' }}> + </div> } } \ No newline at end of file diff --git a/src/helpers.d.ts b/src/helpers.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..27cbb9bcb166ddccae443ba4b17ef8b1880b55c3 --- /dev/null +++ b/src/helpers.d.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +declare module Helpers { + export type Mutable<T> = { + -readonly [P in keyof T]: T[P] + } + export type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array + export type NumberArray = TypedArray | number[] + export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[] +} \ No newline at end of file diff --git a/src/mol-geo/primitive/box.ts b/src/mol-geo/primitive/box.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2ec6c9a4831e052ea208d5693b2aff9d71071bb --- /dev/null +++ b/src/mol-geo/primitive/box.ts @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2018 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-2018 three.js authors + +import { Vec3 } from 'mol-math/linear-algebra' + +export default function Box(width: number, height: number, depth: number, widthSegments: number, heightSegments: number, depthSegments: number) { + widthSegments = Math.floor(widthSegments) + heightSegments = Math.floor(heightSegments) + depthSegments = Math.floor(depthSegments) + + // buffers + const indices: number[] = []; + const vertices: number[] = []; + const normals: number[] = []; + + // helper variables + let numberOfVertices = 0; + + // build each side of the box geometry + buildPlane(2, 1, 0, -1, -1, depth, height, width, depthSegments, heightSegments); // px + buildPlane(2, 1, 0, 1, -1, depth, height, -width, depthSegments, heightSegments); // nx + buildPlane(0, 2, 1, 1, 1, width, depth, height, widthSegments, depthSegments); // py + buildPlane(0, 2, 1, 1, -1, width, depth, -height, widthSegments, depthSegments); // ny + buildPlane(0, 1, 2, 1, -1, width, height, depth, widthSegments, heightSegments); // pz + buildPlane(0, 1, 2, -1, -1, width, height, -depth, widthSegments, heightSegments); // nz + + return { vertices, indices, normals } + + function buildPlane(u: number, v: number, w: number, udir: number, vdir: number, width: number, height: number, depth: number, gridX: number, gridY: number) { + + const segmentWidth = width / gridX; + const segmentHeight = height / gridY; + + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + let vertexCounter = 0; + + const vector = Vec3.zero(); + + // generate vertices and normals + for (let iy = 0; iy < gridY1; ++iy) { + const y = iy * segmentHeight - heightHalf; + for (let ix = 0; ix < gridX1; ++ix) { + const x = ix * segmentWidth - widthHalf; + + // set values to correct vector component + vector[ u ] = x * udir; + vector[ v ] = y * vdir; + vector[ w ] = depthHalf; + + // now apply vector to vertex buffer + vertices.push(...vector); + + // set values to correct vector component + vector[ u ] = 0; + vector[ v ] = 0; + vector[ w ] = depth > 0 ? 1 : -1; + + // now apply vector to normal buffer + normals.push(...vector); + + vertexCounter += 1; + } + } + + // indices + // 1. you need three indices to draw a single face + // 2. a single segment consists of two faces + // 3. so we need to generate six (2*3) indices per segment + for (let iy = 0; iy < gridY; ++iy) { + for (let ix = 0; ix < gridX; ++ix) { + const a = numberOfVertices + ix + gridX1 * iy; + const b = numberOfVertices + ix + gridX1 * (iy + 1); + const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1); + const d = numberOfVertices + (ix + 1) + gridX1 * iy; + + // faces + indices.push(a, b, d); + indices.push(b, c, d); + } + } + + numberOfVertices += vertexCounter; + } +} \ No newline at end of file diff --git a/src/mol-geo/primitive/icosahedron.ts b/src/mol-geo/primitive/icosahedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..b57f0a0d9b72b2a5df477553d32e3565b07337b8 --- /dev/null +++ b/src/mol-geo/primitive/icosahedron.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2018 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-2018 three.js authors + +import Polyhedron from './polyhedron' + +const t = ( 1 + Math.sqrt( 5 ) ) / 2; + +const vertices = [ + - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, + 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, + t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 +]; + +const indices = [ + 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, + 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, + 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, + 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 +]; + +export default function Icosahedron(radius: number, detail: number) { + return Polyhedron(vertices, indices, radius, detail) +} \ No newline at end of file diff --git a/src/mol-geo/primitive/polyhedron.ts b/src/mol-geo/primitive/polyhedron.ts new file mode 100644 index 0000000000000000000000000000000000000000..11c2f9798541386cc755b2acba987b684901704c --- /dev/null +++ b/src/mol-geo/primitive/polyhedron.ts @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2018 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-2018 three.js authors + +import { Vec3 } from 'mol-math/linear-algebra' +import { computeVertexNormals, appplyRadius } from '../util' + +export default function Polyhedron(_vertices: Helpers.NumberArray, _indices: Helpers.NumberArray, radius: number, detail: number) { + radius = radius || 1; + detail = detail || 0; + + const vertices: number[] = []; + const indices: number[] = []; + + // the subdivision creates the vertex buffer data + subdivide(detail); + + // all vertices should lie on a conceptual sphere with a given radius + appplyRadius(vertices, radius); + + const normals = new Float32Array(vertices.length); + computeVertexNormals(vertices, normals) + // this.normalizeNormals(); // smooth normals + + return { vertices, indices, normals } + + // helper functions + + function subdivide(detail: number) { + const a = Vec3.zero() + const b = Vec3.zero() + const c = Vec3.zero() + + // iterate over all faces and apply a subdivison with the given detail value + for (let i = 0; i < _indices.length; i += 3) { + // get the vertices of the face + Vec3.fromArray(a, _vertices, _indices[ i + 0 ] * 3) + Vec3.fromArray(b, _vertices, _indices[ i + 1 ] * 3) + Vec3.fromArray(c, _vertices, _indices[ i + 2 ] * 3) + + // perform subdivision + subdivideFace(a, b, c, detail) + } + + } + + function subdivideFace(a: Vec3, b: Vec3, c: Vec3, detail: number) { + const cols = Math.pow(2, detail) + + // we use this multidimensional array as a data structure for creating the subdivision + const v: Vec3[][] = [] + + // construct all of the vertices for this subdivision + for (let i = 0; i <= cols; ++i) { + v[i] = [] + + const aj = Vec3.zero() + Vec3.lerp(aj, a, c, i / cols) + + const bj = Vec3.zero() + Vec3.lerp(bj, b, c, i / cols) + + const rows = cols - i + for (let j = 0; j <= rows; ++j) { + if (j === 0 && i === cols) { + v[i][j] = aj + } else { + const abj = Vec3.zero() + Vec3.lerp(abj, aj, bj, j / rows) + + v[i][j] = abj + } + } + } + + // construct all of the faces + for (let i = 0; i < cols; ++i) { + for (let j = 0; j < 2 * (cols - i) - 1; ++j) { + const k = Math.floor(j / 2) + if (j % 2 === 0) { + vertices.push(...v[i][k + 1], ...v[i + 1][k], ...v[i][k]) + } else { + vertices.push(...v[i][k + 1], ...v[i + 1][k + 1], ...v[i + 1][k]) + } + } + } + + console.log(v) + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/mol-geo/shape/point.ts b/src/mol-geo/shape/point.ts new file mode 100644 index 0000000000000000000000000000000000000000..5720e664f898570e2de8b3e8808f8199df111f5f --- /dev/null +++ b/src/mol-geo/shape/point.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + + +export function countFromModel(model: any) {} +export function positionFromModel(model: any, array: Float32Array, offset: number) { + +} +export function colorFromModel(model: any, params: any, array: Float32Array, offset: number) { + +} +export function sizeFromModel(model: any, params: any, array: Float32Array, offset: number) { + +} \ No newline at end of file diff --git a/src/mol-geo/shape/sphere.ts b/src/mol-geo/shape/sphere.ts new file mode 100644 index 0000000000000000000000000000000000000000..54cd72a4b8ba4dcf581d87842432de530588078b --- /dev/null +++ b/src/mol-geo/shape/sphere.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ diff --git a/src/mol-geo/util.ts b/src/mol-geo/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..f87508ebb84e0951d92e2392074661f90b03638e --- /dev/null +++ b/src/mol-geo/util.ts @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Vec3 } from 'mol-math/linear-algebra' + +export function normalizeVec3array<T extends Helpers.NumberArray> (a: T) { + const n = a.length + for (let i = 0; i < n; i += 3) { + const x = a[ i ] + const y = a[ i + 1 ] + const z = a[ i + 2 ] + const s = 1 / Math.sqrt(x * x + y * y + z * z) + a[ i ] = x * s + a[ i + 1 ] = y * s + a[ i + 2 ] = z * s + } +} + +export function setArrayZero(array: Helpers.NumberArray) { + const n = array.length + for (let i = 0; i < n; ++i) array[i] = 0 +} + +// iterate over the entire buffer and apply the radius to each vertex +export function appplyRadius(vertices: Helpers.NumberArray, radius: number) { + const v = Vec3.zero() + const n = vertices.length + for (let i = 0; i < n; i += 3) { + Vec3.fromArray(v, vertices, i) + Vec3.normalize(v, v) + Vec3.scale(v, v, radius) + Vec3.toArray(v, vertices, i) + } +} + +// indexed vertex normals weighted by triangle areas http://www.iquilezles.org/www/articles/normals/normals.htm +// normal array must contain only zeros +export function computeIndexedVertexNormals<T extends Helpers.NumberArray> (vertices: Helpers.NumberArray, indices: Helpers.NumberArray, normals: T) { + const a = Vec3.zero() + const b = Vec3.zero() + const c = Vec3.zero() + const cb = Vec3.zero() + const ab = Vec3.zero() + + for (let i = 0, il = indices.length; i < il; i += 3) { + const ai = indices[ i ] * 3 + const bi = indices[ i + 1 ] * 3 + const ci = indices[ i + 2 ] * 3 + + Vec3.fromArray(a, vertices, ai) + Vec3.fromArray(b, vertices, bi) + Vec3.fromArray(c, vertices, ci) + + Vec3.sub(cb, c, b) + Vec3.sub(ab, a, b) + Vec3.cross(cb, cb, ab) + + normals[ ai ] += cb[ 0 ] + normals[ ai + 1 ] += cb[ 1 ] + normals[ ai + 2 ] += cb[ 2 ] + + normals[ bi ] += cb[ 0 ] + normals[ bi + 1 ] += cb[ 1 ] + normals[ bi + 2 ] += cb[ 2 ] + + normals[ ci ] += cb[ 0 ] + normals[ ci + 1 ] += cb[ 1 ] + normals[ ci + 2 ] += cb[ 2 ] + } + + normalizeVec3array(normals) + return normals +} + +// vertex normals for unindexed triangle soup +// normal array must contain only zeros +export function computeVertexNormals<T extends Helpers.NumberArray> (vertices: Helpers.NumberArray, normals: T) { + setArrayZero(normals) + + const a = Vec3.zero() + const b = Vec3.zero() + const c = Vec3.zero() + const cb = Vec3.zero() + const ab = Vec3.zero() + + for (let i = 0, il = vertices.length; i < il; i += 9) { + Vec3.fromArray(a, vertices, i) + Vec3.fromArray(b, vertices, i + 3) + Vec3.fromArray(c, vertices, i + 6) + + Vec3.sub(cb, c, b) + Vec3.sub(ab, a, b) + Vec3.cross(cb, cb, ab) + + normals[ i ] = cb[ 0 ] + normals[ i + 1 ] = cb[ 1 ] + normals[ i + 2 ] = cb[ 2 ] + + normals[ i + 3 ] = cb[ 0 ] + normals[ i + 4 ] = cb[ 1 ] + normals[ i + 5 ] = cb[ 2 ] + + normals[ i + 6 ] = cb[ 0 ] + normals[ i + 7 ] = cb[ 1 ] + normals[ i + 8 ] = cb[ 2 ] + } + + normalizeVec3array(normals) + return normals +} \ No newline at end of file diff --git a/src/mol-gl/attribute.ts b/src/mol-gl/attribute.ts new file mode 100644 index 0000000000000000000000000000000000000000..1133d5e7b57adf73c649c132112d0e7a47433408 --- /dev/null +++ b/src/mol-gl/attribute.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); + +// export type AttributeGroupMutator<T extends AttributesData> = (data: T) => (boolean | void) +// export type AttributeGroupData = { [k: string]: Helpers.TypedArray } +// export type AttributesBuffers<T extends AttributesData> = { [K in keyof T]: REGL.Buffer } + +// interface AttributeGroup<T extends AttributeGroup.Data> { +// readonly buffer: REGL.Buffer +// readonly length: number +// readonly data: T +// setCount(size: number): void +// update(mutator: AttributeGroup.Mutator<T>): void +// } + +// namespace AttributeGroup { +// export type Data = { [k: string]: Helpers.TypedArray } +// export type Mutator<T extends Data> = (data: T) => (UpdateInfo<T> | void) +// export type UpdateInfo<T extends Data> = boolean | { [k in keyof T]: Attribute.UpdateInfo } +// export type Attributes<T extends Data> = { [K in keyof T]: Attribute<T[K]> } + +// export function create<T extends Data>(regl: REGL.Regl, data: T): AttributeGroup<T> { +// const attributes: Attributes<any> = {} +// for (const k of Object.keys(data)) { +// attributes[k] = Attribute.create(regl, data[k]) +// } +// return { +// update: (mutator: Mutator<T>) => { + +// } +// } +// } +// } + + +interface Attribute<T extends Helpers.TypedArray> { + readonly buffer: REGL.AttributeConfig + getCount(): number + setCount(count: number): void + getArray(): T + set(index: number, ...values: number[]): void + update(mutator: Attribute.Mutator<T>): void + reload(): void +} + +interface AttributeProps { + size: 1 | 2 | 3 | 4, + divisor?: number, + offset?: number, + stride?: number +} + +namespace Attribute { + export type Mutator<T extends Helpers.TypedArray> = (data: T) => (UpdateInfo | void) + export type UpdateInfo = boolean | { offset: number, count: number } + + export function create<T extends Helpers.TypedArray>(regl: REGL.Regl, array: T, props: AttributeProps): Attribute<T> { + const itemSize = props.size + let _array = array + let _count = _array.length / itemSize + if (props.stride) _count = _array.length / (props.stride / _array.BYTES_PER_ELEMENT) + console.log(_array.length, props.stride) + const buffer = regl.buffer(_array) + const attribute: REGL.AttributeConfig = { ...props, buffer } + const growIfNeeded = function(count: number) { + if (count * itemSize > _array.length) { + const newArray: T = new (_array as any).constructor(count * itemSize) + newArray.set(_array) + _array = newArray + buffer(_array) + } + _count = count + } + return { + buffer: attribute, + getCount: () => _count, + setCount: (count: number) => growIfNeeded(count), + getArray: () => _array, + set: (index: number, ...values: number[]) => { + if (values.length !== itemSize) throw new Error('wrong number of values given') + growIfNeeded(index) + for (let i = 0; i < itemSize; ++i) { + _array[index * itemSize + i] = values[i] + } + buffer.subdata(values, index * itemSize * _array.BYTES_PER_ELEMENT) + }, + update: (mutator: Mutator<T>, offset?: number, count?: number) => { + if (offset && count) growIfNeeded(offset + count) + mutator(_array) + buffer(_array) + }, + reload: () => buffer(_array) + } + } +} + +export default Attribute \ No newline at end of file diff --git a/src/mol-gl/camera.ts b/src/mol-gl/camera.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a22d2e54c2337eadd5140c56ed1e094b0d91aac --- /dev/null +++ b/src/mol-gl/camera.ts @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/regl-project/regl-camera, + * copyright (c) 2016 Mikola Lysenko. MIT License + */ + +const isBrowser = typeof window !== 'undefined' + +import REGL = require('regl'); + +import mouseChange, { MouseModifiers } from 'mol-util/mouse-change' +import mouseWheel from 'mol-util/mouse-wheel' +import { defaults } from 'mol-util' +import { Mat4, Vec3 } from 'mol-math/linear-algebra/3d' +import { clamp, damp } from 'mol-math/interpolate' + +export interface CameraUniforms { + projection: Mat4, +} + +export interface CameraState { + center: Vec3, + theta: number, + phi: number, + distance: number, + eye: Vec3, + up: Vec3, + fovy: number, + near: number, + far: number, + noScroll: boolean, + flipY: boolean, + dtheta: number, + dphi: number, + rotationSpeed: number, + zoomSpeed: number, + renderOnDirty: boolean, + damping: number, + minDistance: number, + maxDistance: number, +} + +export interface Camera { + update: (props: any, block: any) => void, + setState: (newState: CameraState) => void, + getState: () => CameraState, + isDirty: () => boolean +} + +export namespace Camera { + export function create (regl: REGL.Regl, element: HTMLElement, initialState: Partial<CameraState> = {}): Camera { + const state: CameraState = { + center: defaults(initialState.center, Vec3.zero()), + theta: defaults(initialState.theta, 0), + phi: defaults(initialState.phi, 0), + distance: Math.log(defaults(initialState.distance, 10.0)), + eye: Vec3.zero(), + up: defaults(initialState.up, Vec3.create(0, 1, 0)), + fovy: defaults(initialState.fovy, Math.PI / 4.0), + near: defaults(initialState.near, 0.01), + far: defaults(initialState.far, 1000.0), + noScroll: defaults(initialState.noScroll, false), + flipY: defaults(initialState.flipY, false), + dtheta: 0, + dphi: 0, + rotationSpeed: defaults(initialState.rotationSpeed, 1), + zoomSpeed: defaults(initialState.zoomSpeed, 1), + renderOnDirty: defaults(initialState.renderOnDirty, false), + damping: defaults(initialState.damping, 0.9), + minDistance: Math.log(defaults(initialState.minDistance, 0.1)), + maxDistance: Math.log(defaults(initialState.maxDistance, 1000)) + } + + const view = Mat4.identity() + const projection = Mat4.identity() + + const right = Vec3.create(1, 0, 0) + const front = Vec3.create(0, 0, 1) + + let dirty = false + let ddistance = 0 + + let prevX = 0 + let prevY = 0 + + if (isBrowser) { + const source = element || regl._gl.canvas + + const getWidth = function () { + return element ? element.offsetWidth : window.innerWidth + } + + const getHeight = function () { + return element ? element.offsetHeight : window.innerHeight + } + + mouseChange(source, function (buttons: number, x: number, y: number, mods: MouseModifiers) { + if (buttons & 1) { + const dx = (x - prevX) / getWidth() + const dy = (y - prevY) / getHeight() + + state.dtheta += state.rotationSpeed * 4.0 * dx + state.dphi += state.rotationSpeed * 4.0 * dy + dirty = true; + } + prevX = x + prevY = y + }) + + mouseWheel(source, function (dx: number, dy: number) { + ddistance += dy / getHeight() * state.zoomSpeed + dirty = true; + }, state.noScroll) + } + + function dampAndMarkDirty (x: number) { + const xd = damp(x, state.damping) + if (Math.abs(xd) < 0.1) return 0 + dirty = true; + return xd + } + + function setState (newState: Partial<CameraState> = {}) { + Object.assign(state, newState) + + const { center, eye, up, dtheta, dphi } = state + + state.theta += dtheta + state.phi = clamp(state.phi + dphi, -Math.PI / 2.0, Math.PI / 2.0) + state.distance = clamp(state.distance + ddistance, state.minDistance, state.maxDistance) + + state.dtheta = dampAndMarkDirty(dtheta) + state.dphi = dampAndMarkDirty(dphi) + ddistance = dampAndMarkDirty(ddistance) + + const theta = state.theta + const phi = state.phi + const r = Math.exp(state.distance) + + const vf = r * Math.sin(theta) * Math.cos(phi) + const vr = r * Math.cos(theta) * Math.cos(phi) + const vu = r * Math.sin(phi) + + for (let i = 0; i < 3; ++i) { + eye[i] = center[i] + vf * front[i] + vr * right[i] + vu * up[i] + } + + Mat4.lookAt(view, eye, center, up) + } + + const injectContext = regl({ + context: { + view: () => view, + dirty: () => dirty, + projection: (context: REGL.DefaultContext) => { + Mat4.perspective( + projection, + state.fovy, + context.viewportWidth / context.viewportHeight, + state.near, + state.far + ) + if (state.flipY) { projection[5] *= -1 } + return projection + } + }, + uniforms: { // TODO + view: regl.context('view' as any), + projection: regl.context('projection' as any) + } + }) + + function update (props: any, block: any) { + setState() + injectContext(props, block) + dirty = false + } + + return { + update, + setState, + getState: () => Object.assign({}, state), + isDirty: () => dirty + } + } +} diff --git a/src/mol-gl/model.ts b/src/mol-gl/model.ts new file mode 100644 index 0000000000000000000000000000000000000000..8aaed7823053a5c123ca8ca8e3e1ba9b9bb7cc43 --- /dev/null +++ b/src/mol-gl/model.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import { Mat4, Quat, Vec3 } from 'mol-math/linear-algebra' +import { defaults } from 'mol-util'; + +const tmpMat4 = Mat4() + +type ModelProps = { + rotation?: Quat, + position?: Vec3, + scale?: Vec3 +} + +function createModel(regl: REGL.Regl, props: ModelProps = {}) { + const transform = Mat4.identity() + + const rotation = defaults(props.rotation, Quat.identity()) + const position = defaults(props.position, Vec3.zero()) + const scale = defaults(props.scale, Vec3.create(1, 1, 1)) + + const draw = regl({ + context: { transform, rotation, position, scale }, + + uniforms: { + model(ctx: REGL.DefaultContext, props: any = {}) { + const model = Mat4.identity() + + if ('rotation' in props) Quat.copy(rotation, props.rotation) + if ('position' in props) Vec3.copy(position, props.position) + if ('scale' in props) Vec3.copy(scale, props.scale) + + Mat4.translate(model, model, position) + Mat4.mul(model, model, Mat4.fromQuat(tmpMat4, rotation)) + Mat4.scale(model, model, scale) + + if ('transform' in props) Mat4.mul(model, props.transform, model) + Mat4.copy(transform, model) + + return model + } + } + }) + + return Object.assign(draw, { + get transform() { return transform }, + get position() { return position }, + get rotation() { return rotation }, + get scale() { return scale }, + }) +} + +export default createModel \ No newline at end of file diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d85d090872039bb521f0705034cc9be8e20d0b2 --- /dev/null +++ b/src/mol-gl/renderable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import Attribute from './attribute' +import PointRenderable from './renderable/point' +import MeshRenderable from './renderable/mesh' + +export type AttributesMutator<T extends AttributesData> = (data: T) => (boolean | void) +export type AttributesData = { [k: string]: Helpers.TypedArray } +export type Attributes<T extends AttributesData> = { [K in keyof T]: Attribute<T[K]> } +export type AttributesBuffers<T extends AttributesData> = { [K in keyof T]: REGL.AttributeConfig } + +export interface Renderable<T extends AttributesData> { + draw(): void +} + +export { PointRenderable, MeshRenderable } \ No newline at end of file diff --git a/src/mol-gl/renderable/line.ts b/src/mol-gl/renderable/line.ts new file mode 100644 index 0000000000000000000000000000000000000000..54cd72a4b8ba4dcf581d87842432de530588078b --- /dev/null +++ b/src/mol-gl/renderable/line.ts @@ -0,0 +1,5 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0caa9da7a40c50fe5543db7585885ca2ad67c6d --- /dev/null +++ b/src/mol-gl/renderable/mesh.ts @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import { Renderable } from '../renderable' +import { getBuffers } from './util' +import Attribute from '../attribute'; + +import { MeshShaders } from '../shaders' + +type Mesh = 'mesh' + +type Uniforms = { [k: string]: REGL.Uniform | REGL.Texture } + +export function fillSerial<T extends Helpers.NumberArray> (array: T) { + const n = array.length + for (let i = 0; i < n; ++i) array[ i ] = i + return array +} + +namespace Mesh { + export type DataType = { + position: { type: Float32Array, itemSize: 3 } + normal: { type: Float32Array, itemSize: 3 } + transformColumn0: { type: Float32Array, itemSize: 4 } + transformColumn1: { type: Float32Array, itemSize: 4 } + transformColumn2: { type: Float32Array, itemSize: 4 } + transformColumn3: { type: Float32Array, itemSize: 4 } + } + export type Data = { [K in keyof DataType]: DataType[K]['type'] } + export type Attributes = { [K in keyof Data]: Attribute<Data[K]> } + + export function create(regl: REGL.Regl, attributes: Attributes, uniforms: Uniforms, elements?: Helpers.UintArray): Renderable<Data> { + console.log('mesh', { + count: attributes.position.getCount(), + instances: attributes.transformColumn0.getCount(), + attributes, + uniforms + }) + const instanceCount = attributes.transformColumn0.getCount() + const instanceId = fillSerial(new Float32Array(instanceCount)) + console.log(instanceId) + const command = regl({ + ...MeshShaders, + uniforms: { + objectId: uniforms.objectId || 0, + instanceCount, + ...uniforms + }, + attributes: getBuffers({ + instanceId: Attribute.create(regl, instanceId, { size: 1, divisor: 1 }), + ...attributes + }), + elements: elements && regl.elements({ + data: new Uint16Array(elements), + primitive: 'triangles', + // type: 'uint16', + // count: elements.length / 3, + // length: elements.length * 2 + }), + count: elements ? elements.length : attributes.position.getCount(), + instances: instanceCount, + primitive: 'triangles' + }) + return { + draw: () => command(), + } + } +} + +export default Mesh \ No newline at end of file diff --git a/src/mol-gl/renderable/point.ts b/src/mol-gl/renderable/point.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a075fedf138a124dd1cf2cb838f59961f0940ff --- /dev/null +++ b/src/mol-gl/renderable/point.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import { Renderable } from '../renderable' +import { getBuffers } from './util' +import Attribute from '../attribute'; +import { PointShaders } from '../shaders' + +type Point = 'point' + +namespace Point { + export type DataType = { + position: { type: Float32Array, itemSize: 3 } + transformColumn0: { type: Float32Array, itemSize: 4 } + transformColumn1: { type: Float32Array, itemSize: 4 } + transformColumn2: { type: Float32Array, itemSize: 4 } + transformColumn3: { type: Float32Array, itemSize: 4 } + } + export type Data = { [K in keyof DataType]: DataType[K]['type'] } + export type Attributes = { [K in keyof Data]: Attribute<Data[K]> } + + export function create(regl: REGL.Regl, attributes: Attributes): Renderable<Data> { + console.log('point', { + count: attributes.position.getCount(), + instances: attributes.transformColumn0.getCount(), + }, attributes) + const command = regl({ + ...PointShaders, + attributes: getBuffers(attributes), + count: attributes.position.getCount(), + instances: attributes.transformColumn0.getCount(), + primitive: 'points' + }) + return { + draw: () => command(), + } + } +} + +export default Point + +// namespace Point { +// export type DataType = { +// position: { type: Float32Array, itemSize: 3 } +// } +// export type Data = { [K in keyof DataType]: DataType[K]['type'] } +// export type Attributes = { [K in keyof Data]: Attribute<Data[K]> } + +// export function create(regl: REGL.Regl, dataOrCount: Data | number): Renderable<Data> { +// let count: number +// let data: Data +// if (typeof dataOrCount === 'number') { +// count = dataOrCount +// data = { +// position: new Float32Array(count * 3) +// } +// } else { +// count = dataOrCount.position.length / 3 +// data = dataOrCount +// } +// const attributes = createAttributes(regl, data) +// const command = regl({ +// vert: pointVert, +// frag: pointFrag, +// attributes: getBuffers(attributes), +// count, +// primitive: 'points' +// }) +// return { +// draw: () => command(), +// setCount: (newCount: number) => { +// for (const k of Object.keys(data)) { +// attributes[k as keyof Data].setCount(newCount) +// } +// count = newCount +// }, +// getCount: () => count, +// attributes +// } +// } +// } \ No newline at end of file diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d7d6e3282778042a2128b02c2f7d1e8eeaba8e7 --- /dev/null +++ b/src/mol-gl/renderable/util.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import REGL = require('regl'); +import { Attributes, AttributesData, AttributesBuffers } from '../renderable' +import Attribute from '../attribute' + +export function createTransformAttributes (regl: REGL.Regl, transform: Float32Array) { + const size = 4 + const divisor = 1 + const bpe = transform.BYTES_PER_ELEMENT + const stride = 16 * bpe + return { + transformColumn0: Attribute.create(regl, transform, { size, divisor, offset: 0, stride }), + transformColumn1: Attribute.create(regl, transform, { size, divisor, offset: 4 * bpe, stride }), + transformColumn2: Attribute.create(regl, transform, { size, divisor, offset: 8 * bpe, stride }), + transformColumn3: Attribute.create(regl, transform, { size, divisor, offset: 12 * bpe, stride }) + } +} + +export function getBuffers<T extends AttributesData>(attributes: Attributes<T>): AttributesBuffers<T> { + const buffers: AttributesBuffers<any> = {} + for (const k of Object.keys(attributes)) { + buffers[k] = attributes[k].buffer + } + return buffers as AttributesBuffers<T> +} diff --git a/src/mol-gl/shader/attenuation.glsl b/src/mol-gl/shader/attenuation.glsl new file mode 100644 index 0000000000000000000000000000000000000000..fdc3f4c78c8c86cbc68b8e0b2d9ef109695dd129 --- /dev/null +++ b/src/mol-gl/shader/attenuation.glsl @@ -0,0 +1,14 @@ +// by Tom Madams +// Simple: +// https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/ +// +// Improved +// https://imdoingitwrong.wordpress.com/2011/02/10/improved-light-attenuation/ +float attenuation(float r, float f, float d) { + float denom = d / r + 1.0; + float attenuation = 1.0 / (denom*denom); + float t = (attenuation - f) / (1.0 - f); + return max(t, 0.0); +} + +#pragma glslify: export(attenuation) \ No newline at end of file diff --git a/src/mol-gl/shader/inverse.glsl b/src/mol-gl/shader/inverse.glsl new file mode 100644 index 0000000000000000000000000000000000000000..a8bb99b4b68c4932f76dbc79f3eb31502c581f70 --- /dev/null +++ b/src/mol-gl/shader/inverse.glsl @@ -0,0 +1,70 @@ +// (c) 2014 Mikola Lysenko. MIT License +// https://github.com/glslify/glsl-inverse + +float inverse(float m) { + return 1.0 / m; +} + +mat2 inverse(mat2 m) { + return mat2(m[1][1],-m[0][1], + -m[1][0], m[0][0]) / (m[0][0]*m[1][1] - m[0][1]*m[1][0]); +} + +mat3 inverse(mat3 m) { + float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2]; + float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2]; + float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2]; + + float b01 = a22 * a11 - a12 * a21; + float b11 = -a22 * a10 + a12 * a20; + float b21 = a21 * a10 - a11 * a20; + + float det = a00 * b01 + a01 * b11 + a02 * b21; + + return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11), + b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10), + b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det; +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +#pragma glslify: export(inverse) \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag new file mode 100644 index 0000000000000000000000000000000000000000..4a224d852dcf63e0c5f033f95b6d9997b8c0c87f --- /dev/null +++ b/src/mol-gl/shader/mesh.frag @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +precision mediump float; + +struct Light { + vec3 position; + vec3 color; + vec3 ambient; + float falloff; + float radius; +}; + +uniform Light light; +uniform mat4 view; + +varying vec3 vNormal, vViewPosition, vColor; + +float phongSpecular(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float shininess) { + //Calculate Phong power + vec3 R = -reflect(lightDirection, surfaceNormal); + return pow(max(0.0, dot(viewDirection, R)), shininess); +} + +#define PI 3.14159265 + +float orenNayarDiffuse(vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float roughness, float albedo) { + float LdotV = dot(lightDirection, viewDirection); + float NdotL = dot(lightDirection, surfaceNormal); + float NdotV = dot(surfaceNormal, viewDirection); + + float s = LdotV - NdotL * NdotV; + float t = mix(1.0, max(NdotL, NdotV), step(0.0, s)); + + float sigma2 = roughness * roughness; + float A = 1.0 + sigma2 * (albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33)); + float B = 0.45 * sigma2 / (sigma2 + 0.09); + + return albedo * max(0.0, NdotL) * (A + B * s / t) / PI; +} + +#pragma glslify: attenuation = require(./attenuation.glsl) + +const float specularScale = 0.65; +const float shininess = 10.0; +const float roughness = 5.0; +const float albedo = 0.95; + +void main() { + // determine surface to light direction + vec4 lightPosition = view * vec4(light.position, 1.0); + vec3 lightVector = lightPosition.xyz - vViewPosition; + + // calculate attenuation + float lightDistance = length(lightVector); + float falloff = 1.0; // attenuation(light.radius, light.falloff, lightDistance); + + vec3 L = normalize(lightVector); // light direction + vec3 V = normalize(vViewPosition); // eye direction + vec3 N = normalize(vNormal); // surface normal + + // compute our diffuse & specular terms + float specular = phongSpecular(L, V, N, shininess) * specularScale * falloff; + vec3 diffuse = light.color * orenNayarDiffuse(L, V, N, roughness, albedo) * falloff; + vec3 ambient = light.ambient; + + // add the lighting + vec3 color = vColor * (diffuse + ambient) + specular; + + gl_FragColor.rgb = N; + gl_FragColor.a = 1.0; +} \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert new file mode 100644 index 0000000000000000000000000000000000000000..3c760d523329804a185ff3a0af346911afbed687 --- /dev/null +++ b/src/mol-gl/shader/mesh.vert @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +#define INSTANCE_COLOR + +precision mediump float; + +uniform mat4 projection, model, view; + +uniform int objectId; +uniform int instanceCount; + +#if defined( ATTRIBUTE_COLOR ) + attribute vec3 color; +#elif defined( INSTANCE_COLOR ) || defined( ELEMENT_COLOR ) + uniform vec2 colorTexSize; + uniform sampler2D colorTex; +#endif + +attribute vec3 position; +attribute vec3 normal; +attribute vec4 transformColumn0, transformColumn1, transformColumn2, transformColumn3; +attribute float instanceId; +// attribute int elementId; + +varying vec3 vColor; +varying vec3 vNormal; +varying vec3 vViewPosition; + +#pragma glslify: inverse = require(./inverse.glsl) +#pragma glslify: read_vec3 = require(./read-vec3.glsl) +#pragma glslify: transpose = require(./transpose.glsl) + +void main(){ + #if defined( ATTRIBUTE_COLOR ) + vColor = color; + #elif defined( INSTANCE_COLOR ) + vColor = read_vec3(colorTex, instanceId, colorTexSize); + // #elif defined( ELEMENT_COLOR ) + // vColor = read_vec3(colorTex, instanceId * instanceCount + elementId, colorTexSize); + #else + vColor = vec3(0.0, 1.0, 0.0); + #endif + + mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3); + mat4 modelView = view * model * transform; + + vec4 mvPosition = modelView * vec4(position, 1.0); + vViewPosition = mvPosition.xyz; + gl_Position = projection * mvPosition; + + // TODO do on CPU side + mat3 normalMatrix = transpose(inverse(mat3(modelView))); + vNormal = normalize(normalMatrix * normal); +} \ No newline at end of file diff --git a/src/mol-gl/shader/point.frag b/src/mol-gl/shader/point.frag new file mode 100644 index 0000000000000000000000000000000000000000..329aedf0a19037ee3f74b0cfc34c924574742cfe --- /dev/null +++ b/src/mol-gl/shader/point.frag @@ -0,0 +1,9 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +void main(){ + gl_FragColor = vec4(1, 0, 0, 1); +} \ No newline at end of file diff --git a/src/mol-gl/shader/point.vert b/src/mol-gl/shader/point.vert new file mode 100644 index 0000000000000000000000000000000000000000..26566d293b267a2f5a8600de2415d668e6b06441 --- /dev/null +++ b/src/mol-gl/shader/point.vert @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +precision mediump float; + +uniform mat4 projection, model, view; + +attribute vec3 position; //, color; +attribute vec4 transformColumn0, transformColumn1, transformColumn2, transformColumn3; +// attribute int instanceId; + +// instanced +// attribute mat4 transform; +// uniform mat4 transform; + +// varying vec3 vColor; + +void main(){ + mat4 transform = mat4(transformColumn0, transformColumn1, transformColumn2, transformColumn3); + // vColor = color; + gl_PointSize = 20.0; + gl_Position = projection * view * model * transform * vec4(position, 1.0); +} \ No newline at end of file diff --git a/src/mol-gl/shader/read-vec3.glsl b/src/mol-gl/shader/read-vec3.glsl new file mode 100644 index 0000000000000000000000000000000000000000..7a4b1310529b29d9ffbd6994bd403877f6728748 --- /dev/null +++ b/src/mol-gl/shader/read-vec3.glsl @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +vec3 read_vec3 (sampler2D tex, float i, vec2 size) { + float x = mod(i, size.x); + float y = floor(i / size.x); + vec2 uv = (vec2(x, y) + 0.5) / size; + return texture2D(tex, uv).rgb; +} +#pragma glslify: export(read_vec3) \ No newline at end of file diff --git a/src/mol-gl/shader/transpose.glsl b/src/mol-gl/shader/transpose.glsl new file mode 100644 index 0000000000000000000000000000000000000000..e14ea7e32879b7d8ebc065c961f0f7804c5746f3 --- /dev/null +++ b/src/mol-gl/shader/transpose.glsl @@ -0,0 +1,26 @@ +// (c) 2014 Mikola Lysenko. MIT License +// https://github.com/glslify/glsl-transpose + +float transpose(float m) { + return m; +} + +mat2 transpose(mat2 m) { + return mat2(m[0][0], m[1][0], + m[0][1], m[1][1]); +} + +mat3 transpose(mat3 m) { + return mat3(m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); +} + +mat4 transpose(mat4 m) { + return mat4(m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +#pragma glslify: export(transpose) \ No newline at end of file diff --git a/src/mol-gl/shaders.ts b/src/mol-gl/shaders.ts new file mode 100644 index 0000000000000000000000000000000000000000..51ab7832064e6d122c52f7d80ec5006e908a3adf --- /dev/null +++ b/src/mol-gl/shaders.ts @@ -0,0 +1,12 @@ + +const PointShaders = { + vert: require('mol-gl/shader/point.vert'), + frag: require('mol-gl/shader/point.frag') +} + +const MeshShaders = { + vert: require('mol-gl/shader/mesh.vert'), + frag: require('mol-gl/shader/mesh.frag') +} + +export { PointShaders, MeshShaders } \ No newline at end of file diff --git a/src/mol-gl/util.ts b/src/mol-gl/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..c055b15c8b54f385052b30a72fabbfe11c91a6c1 --- /dev/null +++ b/src/mol-gl/util.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export function calculateTextureInfo (n: number, itemSize: number) { + const sqN = Math.sqrt(n * itemSize) + let width = Math.ceil(sqN) + width = width + (itemSize - (width % itemSize)) % itemSize + const height = width > 0 ? Math.ceil(n * itemSize / width) : 0 + return { width, height, length: width * height * itemSize } +} \ No newline at end of file diff --git a/src/mol-math/interpolate.ts b/src/mol-math/interpolate.ts new file mode 100644 index 0000000000000000000000000000000000000000..83d8f7cd27d24db488aaf11984448a0469001c8c --- /dev/null +++ b/src/mol-math/interpolate.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export function normalize (value: number, min: number, max: number) { + return (value - min) / (max - min) +} + +export function clamp (value: number, min: number, max: number) { + return Math.max(min, Math.min(max, value)) +} + +export function pclamp (value: number) { + return clamp(value, 0, 100) +} + +export function saturate (value: number) { + return clamp(value, 0, 1) +} + +export function damp (value: number, dampingFactor: number) { + const dampedValue = value * dampingFactor + return Math.abs(dampedValue) < 0.1 ? 0 : dampedValue +} + +export function lerp (start: number, stop: number, alpha: number) { + return start + (stop - start) * alpha +} + +export function spline (p0: number, p1: number, p2: number, p3: number, t: number, tension: number) { + const v0 = (p2 - p0) * tension + const v1 = (p3 - p1) * tension + const t2 = t * t + const t3 = t * t2 + return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1 +} + +export function smoothstep (min: number, max: number, x: number) { + x = saturate(normalize(x, min, max)) + return x * x * (3 - 2 * x) +} + +export function smootherstep (min: number, max: number, x: number) { + x = saturate(normalize(x, min, max)) + return x * x * x * (x * (x * 6 - 15) + 10) +} + +export function smootheststep (min: number, max: number, x: number) { + x = saturate(normalize(x, min, max)) + return -20 * Math.pow(x, 7) + 70 * Math.pow(x, 6) - 84 * Math.pow(x, 5) + 35 * Math.pow(x, 4) +} + +export function almostIdentity (value: number, start: number, stop: number) { + if (value > start) return value + const a = 2 * stop - start + const b = 2 * start - 3 * stop + const t = value / start + return (a * t + b) * t * t + stop +} \ No newline at end of file diff --git a/src/mol-math/linear-algebra/3d.ts b/src/mol-math/linear-algebra/3d.ts index 1609b99cf653e3f37380a6f5193cd26cd829551f..727ef49ee3d61f75372de38bd44c84aafee1066c 100644 --- a/src/mol-math/linear-algebra/3d.ts +++ b/src/mol-math/linear-algebra/3d.ts @@ -2,6 +2,7 @@ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ /* @@ -16,9 +17,11 @@ * furnished to do so, subject to the following conditions: */ -export interface Mat4 { [d: number]: number, '@type': 'mat4' } -export interface Vec3 { [d: number]: number, '@type': 'vec3' | 'vec4' } -export interface Vec4 { [d: number]: number, '@type': 'vec4' } +export interface Mat4 extends Array<number> { [d: number]: number, '@type': 'mat4', length: 16 } +export interface Mat3 extends Array<number> { [d: number]: number, '@type': 'mat3', length: 9 } +export interface Vec3 extends Array<number> { [d: number]: number, '@type': 'vec3', length: 3 } +export interface Vec4 extends Array<number> { [d: number]: number, '@type': 'vec4', length: 4 } +export interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 } const enum EPSILON { Value = 0.000001 } @@ -26,6 +29,10 @@ export function Mat4() { return Mat4.zero(); } +export function Quat() { + return Quat.zero(); +} + /** * Stores a 4x4 matrix in a column major (j * 4 + i indexing) format. */ @@ -77,7 +84,7 @@ export namespace Mat4 { mat[15] = 1; return mat; } - + export function ofRows(rows: number[][]): Mat4 { const out = zero(); for (let i = 0; i < 4; i++) { @@ -105,6 +112,44 @@ export namespace Mat4 { a[4 * j + i] = value; } + export function toArray(a: Mat4, out: Helpers.NumberArray, offset: number) { + out[offset + 0] = a[0]; + out[offset + 1] = a[1]; + out[offset + 2] = a[2]; + out[offset + 3] = a[3]; + out[offset + 4] = a[4]; + out[offset + 5] = a[5]; + out[offset + 6] = a[6]; + out[offset + 7] = a[7]; + out[offset + 8] = a[8]; + out[offset + 9] = a[9]; + out[offset + 10] = a[10]; + out[offset + 11] = a[11]; + out[offset + 12] = a[12]; + out[offset + 13] = a[13]; + out[offset + 14] = a[14]; + out[offset + 15] = a[15]; + } + + export function fromArray(a: Mat4, array: Helpers.NumberArray, offset: number) { + a[0] = array[offset + 0] + a[1] = array[offset + 1] + a[2] = array[offset + 2] + a[3] = array[offset + 3] + a[4] = array[offset + 4] + a[5] = array[offset + 5] + a[6] = array[offset + 6] + a[7] = array[offset + 7] + a[8] = array[offset + 8] + a[9] = array[offset + 9] + a[10] = array[offset + 10] + a[11] = array[offset + 11] + a[12] = array[offset + 12] + a[13] = array[offset + 13] + a[14] = array[offset + 14] + a[15] = array[offset + 15] + } + export function copy(out: Mat4, a: Mat4) { out[0] = a[0]; out[1] = a[1]; @@ -129,6 +174,45 @@ export namespace Mat4 { return Mat4.copy(Mat4.zero(), a); } + 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) { + const a01 = a[1], a02 = a[2], a03 = a[3]; + const a12 = a[6], a13 = a[7]; + const a23 = a[11]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a01; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a02; + out[9] = a12; + out[11] = a[14]; + out[12] = a03; + out[13] = a13; + out[14] = a23; + } else { + out[0] = a[0]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a[1]; + out[5] = a[5]; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a[2]; + out[9] = a[6]; + out[10] = a[10]; + out[11] = a[14]; + out[12] = a[3]; + out[13] = a[7]; + out[14] = a[11]; + out[15] = a[15]; + } + return out; + } + export function invert(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], @@ -463,6 +547,213 @@ export namespace Mat4 { } return true; } + + export function fromQuat(out: Mat4, q: Quat) { + const x = q[0], y = q[1], z = q[2], w = q[3]; + const x2 = x + x; + const y2 = y + y; + const z2 = z + z; + + const xx = x * x2; + const yx = y * x2; + const yy = y * y2; + const zx = z * x2; + const zy = z * y2; + const zz = z * z2; + const wx = w * x2; + const wy = w * y2; + const wz = w * z2; + + out[0] = 1 - yy - zz; + out[1] = yx + wz; + out[2] = zx - wy; + out[3] = 0; + + out[4] = yx - wz; + out[5] = 1 - xx - zz; + out[6] = zy + wx; + out[7] = 0; + + out[8] = zx + wy; + out[9] = zy - wx; + out[10] = 1 - xx - yy; + out[11] = 0; + + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + + return out; + } + + /** + * Generates a frustum matrix with the given bounds + */ + export function frustum(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number) { + let rl = 1 / (right - left); + let tb = 1 / (top - bottom); + let nf = 1 / (near - far); + out[0] = (near * 2) * rl; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = (near * 2) * tb; + out[6] = 0; + out[7] = 0; + out[8] = (right + left) * rl; + out[9] = (top + bottom) * tb; + out[10] = (far + near) * nf; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[14] = (far * near * 2) * nf; + out[15] = 0; + return out; + } + + /** + * Generates a perspective projection matrix with the given bounds + */ + export function perspective(out: Mat4, fovy: number, aspect: number, near: number, far: number) { + let f = 1.0 / Math.tan(fovy / 2); + let nf = 1 / (near - far); + out[0] = f / aspect; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = f; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = (far + near) * nf; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[14] = (2 * far * near) * nf; + out[15] = 0; + return out; + } + + /** + * Generates a orthogonal projection matrix with the given bounds + */ + export function ortho(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number) { + let lr = 1 / (left - right); + let bt = 1 / (bottom - top); + let nf = 1 / (near - far); + out[0] = -2 * lr; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = -2 * bt; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 2 * nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = (far + near) * nf; + out[15] = 1; + return out; + } + + /** + * Generates a look-at matrix with the given eye position, focal point, and up axis + */ + export function lookAt(out: Mat4, eye: Vec3, center: Vec3, up: Vec3) { + let x0, x1, x2, y0, y1, y2, z0, z1, z2, len; + let eyex = eye[0]; + let eyey = eye[1]; + let eyez = eye[2]; + let upx = up[0]; + let upy = up[1]; + let upz = up[2]; + let centerx = center[0]; + let centery = center[1]; + let centerz = center[2]; + + if (Math.abs(eyex - centerx) < EPSILON.Value && + Math.abs(eyey - centery) < EPSILON.Value && + Math.abs(eyez - centerz) < EPSILON.Value + ) { + return setIdentity(out); + } + + z0 = eyex - centerx; + z1 = eyey - centery; + z2 = eyez - centerz; + + len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2); + z0 *= len; + z1 *= len; + z2 *= len; + + x0 = upy * z2 - upz * z1; + x1 = upz * z0 - upx * z2; + x2 = upx * z1 - upy * z0; + len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (!len) { + x0 = 0; + x1 = 0; + x2 = 0; + } else { + len = 1 / len; + x0 *= len; + x1 *= len; + x2 *= len; + } + + y0 = z1 * x2 - z2 * x1; + y1 = z2 * x0 - z0 * x2; + y2 = z0 * x1 - z1 * x0; + + len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (!len) { + y0 = 0; + y1 = 0; + y2 = 0; + } else { + len = 1 / len; + y0 *= len; + y1 *= len; + y2 *= len; + } + + out[0] = x0; + out[1] = y0; + out[2] = z0; + out[3] = 0; + out[4] = x1; + out[5] = y1; + out[6] = z1; + out[7] = 0; + out[8] = x2; + out[9] = y2; + out[10] = z2; + out[11] = 0; + out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); + out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); + out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); + out[15] = 1; + + return out; + } +} + +export namespace Mat3 { + export function zero(): Mat3 { + // force double backing array by 0.1. + const ret = [0.1, 0, 0, 0, 0, 0, 0, 0, 0]; + ret[0] = 0.0; + return ret as any; + } } export namespace Vec3 { @@ -488,6 +779,18 @@ export namespace Vec3 { return { x: v[0], y: v[1], z: v[2] }; } + export function fromArray(v: Vec3, array: Helpers.NumberArray, offset: number) { + v[0] = array[offset + 0] + v[1] = array[offset + 1] + v[2] = array[offset + 2] + } + + export function toArray(v: Vec3, out: Helpers.NumberArray, offset: number) { + out[offset + 0] = v[0] + out[offset + 1] = v[1] + out[offset + 2] = v[2] + } + export function create(x: number, y: number, z: number): Vec3 { const out = zero(); out[0] = x; @@ -668,6 +971,14 @@ export namespace Vec4 { return out; } + export function copy(out: Vec4, a: Vec4) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + export function set(out: Vec4, x: number, y: number, z: number, w: number) { out[0] = x; out[1] = y; @@ -676,6 +987,14 @@ export namespace Vec4 { return out; } + export function add(out: Quat, a: Quat, b: Quat) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; + } + export function distance(a: Vec4, b: Vec4) { const x = b[0] - a[0], y = b[1] - a[1], @@ -716,4 +1035,354 @@ export namespace Vec4 { out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; return out; } +} + +export namespace Quat { + export function zero(): Quat { + // force double backing array by 0.1. + const ret = [0.1, 0, 0, 0]; + ret[0] = 0.0; + return ret as any; + } + + export function identity(): Quat { + const out = zero(); + out[3] = 1; + return out; + } + + export function create(x: number, y: number, z: number, w: number) { + const out = identity(); + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; + } + + export function setAxisAngle(out: Quat, axis: Vec3, rad: number) { + rad = rad * 0.5; + let s = Math.sin(rad); + out[0] = s * axis[0]; + out[1] = s * axis[1]; + out[2] = s * axis[2]; + out[3] = Math.cos(rad); + return out; + } + + /** + * Gets the rotation axis and angle for a given + * quaternion. If a quaternion is created with + * setAxisAngle, this method will return the same + * values as providied in the original parameter list + * OR functionally equivalent values. + * Example: The quaternion formed by axis [0, 0, 1] and + * angle -90 is the same as the quaternion formed by + * [0, 0, 1] and 270. This method favors the latter. + */ + export function getAxisAngle(out_axis: Vec3, q: Quat) { + let rad = Math.acos(q[3]) * 2.0; + let s = Math.sin(rad / 2.0); + if (s !== 0.0) { + out_axis[0] = q[0] / s; + out_axis[1] = q[1] / s; + out_axis[2] = q[2] / s; + } else { + // If s is zero, return any axis (no rotation - axis does not matter) + out_axis[0] = 1; + out_axis[1] = 0; + out_axis[2] = 0; + } + return rad; + } + + export function multiply(out: Quat, a: Quat, b: Quat) { + let ax = a[0], ay = a[1], az = a[2], aw = a[3]; + let bx = b[0], by = b[1], bz = b[2], bw = b[3]; + + out[0] = ax * bw + aw * bx + ay * bz - az * by; + out[1] = ay * bw + aw * by + az * bx - ax * bz; + out[2] = az * bw + aw * bz + ax * by - ay * bx; + out[3] = aw * bw - ax * bx - ay * by - az * bz; + return out; + } + + export function rotateX(out: Quat, a: Quat, rad: number) { + rad *= 0.5; + + let ax = a[0], ay = a[1], az = a[2], aw = a[3]; + let bx = Math.sin(rad), bw = Math.cos(rad); + + out[0] = ax * bw + aw * bx; + out[1] = ay * bw + az * bx; + out[2] = az * bw - ay * bx; + out[3] = aw * bw - ax * bx; + return out; + } + + export function rotateY(out: Quat, a: Quat, rad: number) { + rad *= 0.5; + + let ax = a[0], ay = a[1], az = a[2], aw = a[3]; + let by = Math.sin(rad), bw = Math.cos(rad); + + out[0] = ax * bw - az * by; + out[1] = ay * bw + aw * by; + out[2] = az * bw + ax * by; + out[3] = aw * bw - ay * by; + return out; + } + + export function rotateZ(out: Quat, a: Quat, rad: number) { + rad *= 0.5; + + let ax = a[0], ay = a[1], az = a[2], aw = a[3]; + let bz = Math.sin(rad), bw = Math.cos(rad); + + out[0] = ax * bw + ay * bz; + out[1] = ay * bw - ax * bz; + out[2] = az * bw + aw * bz; + out[3] = aw * bw - az * bz; + return out; + } + + /** + * Calculates the W component of a quat from the X, Y, and Z components. + * Assumes that quaternion is 1 unit in length. + * Any existing W component will be ignored. + */ + export function calculateW(out: Quat, a: Quat) { + let x = a[0], y = a[1], z = a[2]; + + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z)); + return out; + } + + /** + * Performs a spherical linear interpolation between two quat + */ + export function slerp(out: Quat, a: Quat, b: Quat, t: number) { + // benchmarks: + // http://jsperf.com/quaternion-slerp-implementations + let ax = a[0], ay = a[1], az = a[2], aw = a[3]; + let bx = b[0], by = b[1], bz = b[2], bw = b[3]; + + let omega, cosom, sinom, scale0, scale1; + + // calc cosine + cosom = ax * bx + ay * by + az * bz + aw * bw; + // adjust signs (if necessary) + if ( cosom < 0.0 ) { + cosom = -cosom; + bx = - bx; + by = - by; + bz = - bz; + bw = - bw; + } + // calculate coefficients + if ( (1.0 - cosom) > 0.000001 ) { + // standard case (slerp) + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0 - t; + scale1 = t; + } + // calculate final values + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + + return out; + } + + export function invert(out: Quat, a: Quat) { + let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3]; + let dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3; + let invDot = dot ? 1.0/dot : 0; + + // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0 + + out[0] = -a0 * invDot; + out[1] = -a1 * invDot; + out[2] = -a2 * invDot; + out[3] = a3 * invDot; + return out; + } + + /** + * Calculates the conjugate of a quat + * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result. + */ + export function conjugate(out: Quat, a: Quat) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a[3]; + return out; + } + + /** + * Creates a quaternion from the given 3x3 rotation matrix. + * + * NOTE: The resultant quaternion is not normalized, so you should be sure + * to renormalize the quaternion yourself where necessary. + */ + export function fromMat3(out: Quat, m: Mat3) { + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "Quaternion Calculus and Fast Animation". + const fTrace = m[0] + m[4] + m[8]; + let fRoot; + + if ( fTrace > 0.0 ) { + // |w| > 1/2, may as well choose w > 1/2 + fRoot = Math.sqrt(fTrace + 1.0); // 2w + out[3] = 0.5 * fRoot; + fRoot = 0.5/fRoot; // 1/(4w) + out[0] = (m[5]-m[7])*fRoot; + out[1] = (m[6]-m[2])*fRoot; + out[2] = (m[1]-m[3])*fRoot; + } else { + // |w| <= 1/2 + let i = 0; + if ( m[4] > m[0] ) i = 1; + if ( m[8] > m[i*3+i] ) i = 2; + let j = (i+1)%3; + let k = (i+2)%3; + + fRoot = Math.sqrt(m[i*3+i]-m[j*3+j]-m[k*3+k] + 1.0); + out[i] = 0.5 * fRoot; + fRoot = 0.5 / fRoot; + out[3] = (m[j*3+k] - m[k*3+j]) * fRoot; + out[j] = (m[j*3+i] + m[i*3+j]) * fRoot; + out[k] = (m[k*3+i] + m[i*3+k]) * fRoot; + } + + return out; + } + + export function clone(a: Quat) { + const out = zero(); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + + export function copy(out: Quat, a: Quat) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; + } + + export function set(out: Quat, x: number, y: number, z: number, w: number) { + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; + } + + export function add(out: Quat, a: Quat, b: Quat) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; + } + + export function normalize(out: Quat, a: Quat) { + let x = a[0]; + let y = a[1]; + let z = a[2]; + let w = a[3]; + let len = x*x + y*y + z*z + w*w; + if (len > 0) { + len = 1 / Math.sqrt(len); + out[0] = x * len; + out[1] = y * len; + out[2] = z * len; + out[3] = w * len; + } + return out; + } + + /** + * Sets a quaternion to represent the shortest rotation from one + * vector to another. + * + * Both vectors are assumed to be unit length. + */ + const rotTmpVec3 = Vec3.zero(); + const rotTmpVec3UnitX = Vec3.create(1, 0, 0); + const rotTmpVec3UnitY = Vec3.create(0, 1, 0); + export function rotationTo(out: Quat, a: Vec3, b: Vec3) { + let dot = Vec3.dot(a, b); + if (dot < -0.999999) { + Vec3.cross(rotTmpVec3, rotTmpVec3UnitX, a); + if (Vec3.magnitude(rotTmpVec3) < 0.000001) + Vec3.cross(rotTmpVec3, rotTmpVec3UnitY, a); + Vec3.normalize(rotTmpVec3, rotTmpVec3); + setAxisAngle(out, rotTmpVec3, Math.PI); + return out; + } else if (dot > 0.999999) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; + } else { + Vec3.cross(rotTmpVec3, a, b); + out[0] = rotTmpVec3[0]; + out[1] = rotTmpVec3[1]; + out[2] = rotTmpVec3[2]; + out[3] = 1 + dot; + return normalize(out, out); + } + } + + /** + * Performs a spherical linear interpolation with two control points + */ + let sqlerpTemp1 = Quat.zero(); + let sqlerpTemp2 = Quat.zero(); + export function sqlerp(out: Quat, a: Quat, b: Quat, c: Quat, d: Quat, t: number) { + slerp(sqlerpTemp1, a, d, t); + slerp(sqlerpTemp2, b, c, t); + slerp(out, sqlerpTemp1, sqlerpTemp2, 2 * t * (1 - t)); + return out; + } + + /** + * Sets the specified quaternion with values corresponding to the given + * axes. Each axis is a vec3 and is expected to be unit length and + * perpendicular to all other specified axes. + */ + const axesTmpMat = Mat3.zero(); + export function setAxes(out: Quat, view: Vec3, right: Vec3, up: Vec3) { + axesTmpMat[0] = right[0]; + axesTmpMat[3] = right[1]; + axesTmpMat[6] = right[2]; + + axesTmpMat[1] = up[0]; + axesTmpMat[4] = up[1]; + axesTmpMat[7] = up[2]; + + axesTmpMat[2] = -view[0]; + axesTmpMat[5] = -view[1]; + axesTmpMat[8] = -view[2]; + + return normalize(out, Quat.fromMat3(out, axesTmpMat)); + } } \ No newline at end of file diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 4d540010c66a0f3d2033c4e39c81da8a205a3775..b40fb72acb11c6c1f045ff00d4f698f43d868762 100644 --- a/src/mol-util/index.ts +++ b/src/mol-util/index.ts @@ -21,4 +21,8 @@ export function arrayEqual<T>(arr1: T[], arr2: T[]) { } } return true +} + +export function defaults (value: any, defaultValue: any) { + return value !== undefined ? value : defaultValue } \ No newline at end of file diff --git a/src/mol-util/mouse-change.ts b/src/mol-util/mouse-change.ts new file mode 100644 index 0000000000000000000000000000000000000000..96d7f87a1f359afd9feecb06f934583debe81b8c --- /dev/null +++ b/src/mol-util/mouse-change.ts @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/mikolalysenko/mouse-change, + * copyright (c) 2015 Mikola Lysenko. MIT License + */ + +import * as mouse from './mouse-event' + +export type MouseModifiers = { + shift: boolean, + alt: boolean, + control: boolean, + meta: boolean +} +export type MouseChangeCallback = (buttonState: number, x: number, y: number, mods: MouseModifiers) => void + +export default function mouseListen (element: Element, callback: MouseChangeCallback) { + let buttonState = 0 + let x = 0 + let y = 0 + const mods: MouseModifiers = { + shift: false, + alt: false, + control: false, + meta: false + } + let attached = false + + function updateMods (event: MouseEvent | KeyboardEvent) { + let changed = false + if ('altKey' in event) { + changed = changed || event.altKey !== mods.alt + mods.alt = !!event.altKey + } + if ('shiftKey' in event) { + changed = changed || event.shiftKey !== mods.shift + mods.shift = !!event.shiftKey + } + if ('ctrlKey' in event) { + changed = changed || event.ctrlKey !== mods.control + mods.control = !!event.ctrlKey + } + if ('metaKey' in event) { + changed = changed || event.metaKey !== mods.meta + mods.meta = !!event.metaKey + } + return changed + } + + function handleEvent (nextButtons: number, event: MouseEvent) { + const nextX = mouse.x(event) + const nextY = mouse.y(event) + if ('buttons' in event) { + nextButtons = event.buttons | 0 + } + if (nextButtons !== buttonState || nextX !== x || nextY !== y || updateMods(event) ) { + buttonState = nextButtons | 0 + x = nextX || 0 + y = nextY || 0 + callback && callback(buttonState, x, y, mods) + } + } + + function clearState (event: MouseEvent) { + handleEvent(0, event) + } + + function handleBlur () { + if (buttonState || x || y || mods.shift || mods.alt || mods.meta || mods.control) { + x = y = 0 + buttonState = 0 + mods.shift = mods.alt = mods.control = mods.meta = false + callback && callback(0, 0, 0, mods) + } + } + + function handleMods (event: MouseEvent | KeyboardEvent) { + if (updateMods(event)) { + callback && callback(buttonState, x, y, mods) + } + } + + function handleMouseMove (event: MouseEvent) { + if (mouse.buttons(event) === 0) { + handleEvent(0, event) + } else { + handleEvent(buttonState, event) + } + } + + function handleMouseDown (event: MouseEvent) { + handleEvent(buttonState | mouse.buttons(event), event) + } + + function handleMouseUp (event: MouseEvent) { + handleEvent(buttonState & ~mouse.buttons(event), event) + } + + function attachListeners () { + if (attached) return + attached = true + + element.addEventListener('mousemove', handleMouseMove as EventListener) + element.addEventListener('mousedown', handleMouseDown as EventListener) + element.addEventListener('mouseup', handleMouseUp as EventListener) + + element.addEventListener('mouseleave', clearState as EventListener) + element.addEventListener('mouseenter', clearState as EventListener) + element.addEventListener('mouseout', clearState as EventListener) + element.addEventListener('mouseover', clearState as EventListener) + + element.addEventListener('blur', handleBlur) + element.addEventListener('keyup', handleMods as EventListener) + element.addEventListener('keydown', handleMods as EventListener) + element.addEventListener('keypress', handleMods as EventListener) + + if (!(element instanceof Window)) { + window.addEventListener('blur', handleBlur) + window.addEventListener('keyup', handleMods) + window.addEventListener('keydown', handleMods) + window.addEventListener('keypress', handleMods) + } + } + + function detachListeners () { + if (!attached) return + attached = false + + element.removeEventListener('mousemove', handleMouseMove as EventListener) + element.removeEventListener('mousedown', handleMouseDown as EventListener) + element.removeEventListener('mouseup', handleMouseUp as EventListener) + + element.removeEventListener('mouseleave', clearState as EventListener) + element.removeEventListener('mouseenter', clearState as EventListener) + element.removeEventListener('mouseout', clearState as EventListener) + element.removeEventListener('mouseover', clearState as EventListener) + + element.removeEventListener('blur', handleBlur) + element.removeEventListener('keyup', handleMods as EventListener) + element.removeEventListener('keydown', handleMods as EventListener) + element.removeEventListener('keypress', handleMods as EventListener) + + if (!(element instanceof Window)) { + window.removeEventListener('blur', handleBlur) + window.removeEventListener('keyup', handleMods) + window.removeEventListener('keydown', handleMods) + window.removeEventListener('keypress', handleMods) + } + } + + // Attach listeners + attachListeners() + + const result = { + element: element + } + + Object.defineProperties(result, { + enabled: { + get: function () { return attached }, + set: function (f) { + if (f) { + attachListeners() + } else { + detachListeners() + } + }, + enumerable: true + }, + buttons: { + get: function () { return buttonState }, + enumerable: true + }, + x: { + get: function () { return x }, + enumerable: true + }, + y: { + get: function () { return y }, + enumerable: true + }, + mods: { + get: function () { return mods }, + enumerable: true + } + }) + + return result +} \ No newline at end of file diff --git a/src/mol-util/mouse-event.ts b/src/mol-util/mouse-event.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f38bc7cb98c6de03d8474ed11154d9e70f1f2ba --- /dev/null +++ b/src/mol-util/mouse-event.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/mikolalysenko/mouse-event, + * copyright (c) 2015 Mikola Lysenko. MIT License + */ + +export function buttons(event: MouseEvent) { + if (typeof event === 'object') { + if ('buttons' in event) { + return event.buttons + } else if ('which' in event) { + const b = (event as any).which // 'any' to support older browsers + if (b === 2) { + return 4 + } else if (b === 3) { + return 2 + } else if (b > 0) { + return 1<<(b-1) + } + } else if ('button' in event) { + const b = (event as any).button // 'any' to support older browsers + if (b === 1) { + return 4 + } else if (b === 2) { + return 2 + } else if (b >= 0) { + return 1<<b + } + } + } + return 0 +} + +export function element(event: MouseEvent) { + return event.target as Element || event.srcElement || window +} + +export function x(event: MouseEvent) { + if (typeof event === 'object') { + if ('offsetX' in event) { + return event.offsetX + } + const target = element(event) + const bounds = target.getBoundingClientRect() + return (event as any).clientX - bounds.left // 'any' to support older browsers + } + return 0 +} + +export function y(event: MouseEvent) { + if (typeof event === 'object') { + if ('offsetY' in event) { + return event.offsetY + } + const target = element(event) + const bounds = target.getBoundingClientRect() + return (event as any).clientY - bounds.top // 'any' to support older browsers + } + return 0 +} \ No newline at end of file diff --git a/src/mol-util/mouse-wheel.ts b/src/mol-util/mouse-wheel.ts new file mode 100644 index 0000000000000000000000000000000000000000..51a11f7853825370729bdd5d0aba76f015d3b310 --- /dev/null +++ b/src/mol-util/mouse-wheel.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/mikolalysenko/mouse-wheel, + * copyright (c) 2015 Mikola Lysenko. MIT License + */ + +import toPixels from './to-pixels' + +export type MouseWheelCallback = (dx: number, dy: number, dz: number, event: MouseWheelEvent) => void + +export default function mouseWheelListen(element: Element, callback: MouseWheelCallback, noScroll = false) { + const lineHeight = toPixels('ex', element) + const listener = function (event: MouseWheelEvent) { + if (noScroll) { + event.preventDefault() + } + const mode = event.deltaMode + let dx = event.deltaX || 0 + let dy = event.deltaY || 0 + let dz = event.deltaZ || 0 + let scale = 1 + switch (mode) { + case 1: scale = lineHeight; break + case 2: scale = window.innerHeight; break + } + dx *= scale + dy *= scale + dz *= scale + if (dx || dy || dz) { + return callback(dx, dy, dz, event) + } + } + element.addEventListener('wheel', listener) + return listener +} \ No newline at end of file diff --git a/src/mol-util/parse-unit.ts b/src/mol-util/parse-unit.ts new file mode 100644 index 0000000000000000000000000000000000000000..4071eec017d1810712f2a0e77438908337f97a36 --- /dev/null +++ b/src/mol-util/parse-unit.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/mattdesl/parse-unit, + * copyright (c) 2014 Matt DesLauriers. MIT License + */ + +const reUnit = /[\d.\-\+]*\s*(.*)/ + +export default function parseUnit(str: string, out: [number, string] = [ 0, '' ]) { + str = String(str) + const num = parseFloat(str) + out[0] = num + const m = str.match(reUnit) + if (m) out[1] = m[1] || '' + return out +} \ No newline at end of file diff --git a/src/mol-util/to-pixels.ts b/src/mol-util/to-pixels.ts new file mode 100644 index 0000000000000000000000000000000000000000..870404025b91e58164aee11dc0ddd561d2da557e --- /dev/null +++ b/src/mol-util/to-pixels.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/* + * This code has been modified from https://github.com/mikolalysenko/to-px, + * copyright (c) 2015 Mikola Lysenko. MIT License + */ + +import parseUnit from './parse-unit' + +const PIXELS_PER_INCH = 96 + +function getPropertyInPX(element: Element, prop: string) { + const parts = parseUnit(getComputedStyle(element).getPropertyValue(prop)) + return parts[0] * toPixels(parts[1], element) +} + +// This brutal hack is needed +function getSizeBrutal(unit: string, element: Element) { + const testDIV = document.createElement('div') + testDIV.style.setProperty('font-size', '128' + unit) + element.appendChild(testDIV) + const size = getPropertyInPX(testDIV, 'font-size') / 128 + element.removeChild(testDIV) + return size +} + +export default function toPixels(str: string, element: Element = document.body): number { + str = (str || 'px').trim().toLowerCase() + switch (str) { + case '%': // Ambiguous, not sure if we should use width or height + return element.clientHeight / 100.0 + case 'ch': + case 'ex': + return getSizeBrutal(str, element) + case 'em': + return getPropertyInPX(element, 'font-size') + case 'rem': + return getPropertyInPX(document.body, 'font-size') + case 'vw': + return window.innerWidth/100 + case 'vh': + return window.innerHeight/100 + case 'vmin': + return Math.min(window.innerWidth, window.innerHeight) / 100 + case 'vmax': + return Math.max(window.innerWidth, window.innerHeight) / 100 + case 'in': + return PIXELS_PER_INCH + case 'cm': + return PIXELS_PER_INCH / 2.54 + case 'mm': + return PIXELS_PER_INCH / 25.4 + case 'pt': + return PIXELS_PER_INCH / 72 + case 'pc': + return PIXELS_PER_INCH / 6 + } + return 1 +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index f8761db56ee42b220f37b1a7991e4209521b7694..8c7f443865474f7221f0ef0cd3a0fcb2035ec020 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,13 +14,14 @@ "outDir": "build/node_modules", "baseUrl": "src", "paths": { - "mol-task": ["./mol-task", "./mol-task/index.ts"], - "mol-comp": ["./mol-comp", "./mol-comp/index.ts"], - "mol-util": ["./mol-util", "./mol-util/index.ts"], "mol-data": ["./mol-data", "./mol-data/index.ts"], - "mol-math": ["./mol-math"], + "mol-gl": ["./mol-gl"], "mol-io": ["./mol-io"], - "mol-model": ["./mol-model"] + "mol-math": ["./mol-math"], + "mol-model": ["./mol-model"], + "mol-ql": ["./mol-ql"], + "mol-task": ["./mol-task", "./mol-task/index.ts"], + "mol-util": ["./mol-util", "./mol-util/index.ts"], } }, "include": [ "**/*" ] diff --git a/web/render-test/index.html b/web/render-test/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2e144b29791e36ed9926e7d0785e4ffae563cc7d --- /dev/null +++ b/web/render-test/index.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=1" /> + <title>Mol* Render Test</title> + </head> + <body> + <div id="app"></div> + <script src="./index.js"></script> + </body> +</html> \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..50bbc097e6756fd979282cc0df0fbd5a06e1fb53 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,23 @@ +const path = require('path'); +const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin'); +module.exports = { + module: { + rules: [ + { + loader: 'raw-loader', + test: /\.(glsl|frag|vert)$/, + include: [ path.resolve(__dirname, "build/node_modules/") ], + }, + { + loader: 'glslify-loader', + test: /\.(glsl|frag|vert)$/, + include: [ path.resolve(__dirname, "build/node_modules/") ] + } + ] + }, + plugins: [ + new ExtraWatchWebpackPlugin({ + files: [ './**/*.vert', './**/*.frag', './**/*.glsl' ], + }), + ], +} \ No newline at end of file