diff --git a/src/mol-geo/color/color.ts b/src/mol-geo/color/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..f7dded0b9b9998a16f89bde0eece96084c77ecd4 --- /dev/null +++ b/src/mol-geo/color/color.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/** RGB color triplet expressed as a single number */ +type Color = number + +namespace Color { + export function toRgb(hexColor: Color) { + return [ hexColor >> 16 & 255, hexColor >> 8 & 255, hexColor & 255 ] + } + + export function toRgbNormalized(hexColor: Color) { + return [ (hexColor >> 16 & 255) / 255, (hexColor >> 8 & 255) / 255, (hexColor & 255) / 255 ] + } + + export function fromRgb(r: number, g: number, b: number ): Color { + return (r << 16) | (g << 8) | b + } + + /** Copies hex color to rgb array */ + export function toArray(hexColor: Color, array: Helpers.NumberArray, offset: number) { + array[ offset ] = (hexColor >> 16 & 255) + array[ offset + 1 ] = (hexColor >> 8 & 255) + array[ offset + 2 ] = (hexColor & 255) + } + + /** Copies normalized (0 to 1) hex color to rgb array */ + export function toArrayNormalized(hexColor: Color, array: Helpers.NumberArray, offset: number) { + array[ offset ] = (hexColor >> 16 & 255) / 255 + array[ offset + 1 ] = (hexColor >> 8 & 255) / 255 + array[ offset + 2 ] = (hexColor & 255) / 255 + } + + /** Linear interpolation between two colors */ + export function interpolate(c1: Color, c2: Color, t: number): Color { + const r1 = c1 >> 16 & 255 + const g1 = c1 >> 8 & 255 + const b1 = c1 & 255 + const r2 = c2 >> 16 & 255 + const g2 = c2 >> 8 & 255 + const b2 = c2 & 255 + + const r = r1 + (r2 - r1) * t + const g = g1 + (g2 - g1) * t + const b = b1 + (b2 - b1) * t + + return (r << 16) | (g << 8) | b + } +} + +export default Color \ No newline at end of file diff --git a/src/mol-geo/color/data.ts b/src/mol-geo/color/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..03302f011316d52e5f43a2c4b1ed831aad48b77a --- /dev/null +++ b/src/mol-geo/color/data.ts @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from 'mol-util'; +import Color from './color'; +import { Mesh } from '../shape/mesh'; + +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 } +} + +interface ColorTexture extends Uint8Array { + width: number, + height: number +} + +function createColorTexture (n: number): ColorTexture { + const colorTexInfo = calculateTextureInfo(n, 3) + const colorTexture = new Uint8Array(colorTexInfo.length) + return Object.assign(colorTexture, { + width: colorTexInfo.width, + height: colorTexInfo.height + }) +} + +export type UniformColor = { type: 'uniform', value: number[] } +export type AttributeColor = { type: 'attribute', value: ValueCell<Float32Array> } +export type InstanceColor = { type: 'instance', value: ValueCell<ColorTexture> } +export type ElementColor = { type: 'element', value: ValueCell<ColorTexture> } +export type ElementInstanceColor = { type: 'element-instance', value: ValueCell<ColorTexture> } +export type ColorData = UniformColor | AttributeColor | InstanceColor | ElementColor | ElementInstanceColor + +export interface UniformColorProps { + value: Color +} + +/** Creates color uniform */ +export function createUniformColor(props: UniformColorProps): UniformColor { + return { type: 'uniform', value: Color.toRgbNormalized(props.value) } +} + +export interface AttributeColorProps { + colorFn: (elementIdx: number) => Color + vertexCount: number, + offsetCount: number, + offsets: ValueCell<Uint32Array> +} + +/** Creates color attribute with color for each element (i.e. shared across indtances/units) */ +export function createAttributeColor(props: AttributeColorProps): AttributeColor { + const { colorFn, vertexCount, offsetCount, offsets} = props + const colors = new Float32Array(vertexCount * 3); + const _offsets = offsets.ref.value + for (let i = 0, il = offsetCount - 1; i < il; ++i) { + const start = _offsets[i] + const end = _offsets[i + 1] + const hexColor = colorFn(i) + for (let i = start, il = end; i < il; ++i) { + Color.toArrayNormalized(hexColor, colors, i * 3) + } + } + return { type: 'attribute', value: ValueCell.create(colors) } +} + +export interface InstanceColorProps { + colorFn: (unitIdx: number) => Color + unitCount: number +} + +/** Creates color texture with color for each instance/unit */ +export function createInstanceColor(props: InstanceColorProps): InstanceColor { + const { colorFn, unitCount} = props + const colors = createColorTexture(unitCount) + for (let i = 0; i < unitCount; i++) { + Color.toArray(colorFn(i), colors, i * 3) + } + return { type: 'instance', value: ValueCell.create(colors) } +} + +export interface ElementColorProps { + colorFn: (elementIdx: number) => Color + offsetCount: number, + offsets: ValueCell<Uint32Array> +} + +/** Creates color texture with color for each element (i.e. shared across indtances/units) */ +export function createElementColor(props: ElementColorProps): ElementColor { + const { colorFn, offsetCount } = props + const elementCount = offsetCount - 1 + const colors = createColorTexture(elementCount) + for (let i = 0, il = elementCount; i < il; ++i) { + Color.toArray(colorFn(i), colors, i * 3) + } + return { type: 'element', value: ValueCell.create(colors) } +} + +export interface ElementInstanceColorProps { + colorFn: (unitIdx: number, elementIdx: number) => Color + unitCount: number, + offsetCount: number, + offsets: ValueCell<Uint32Array> +} + +/** Creates color texture with color for each element instance (i.e. for each unit) */ +export function createElementInstanceColor(props: ElementInstanceColorProps): ElementInstanceColor { + const { colorFn, unitCount, offsetCount } = props + const elementCount = offsetCount - 1 + const count = unitCount * elementCount + const colors = createColorTexture(count) + let colorOffset = 0 + for (let i = 0; i < unitCount; i++) { + for (let j = 0, jl = elementCount; j < jl; ++j) { + Color.toArray(colorFn(i, j), colors, colorOffset) + colorOffset += 3 + } + } + return { type: 'element-instance', value: ValueCell.create(colors) } +} + +/** Create color attribute or texture, depending on the mesh */ +export function createAttributeOrElementColor(mesh: Mesh, props: AttributeColorProps) { + return mesh.vertexCount < 4 * mesh.offsetCount ? createAttributeColor(props) : createElementColor(props) +} \ No newline at end of file diff --git a/src/mol-geo/color/index.ts b/src/mol-geo/color/index.ts index 5abc3247586dad0e505016c57e749b2b37b2ae9d..e1f138de4862ebe331aacd0afdf48db86b864319 100644 --- a/src/mol-geo/color/index.ts +++ b/src/mol-geo/color/index.ts @@ -4,84 +4,8 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { ColorBrewer } from './tables' +import Color from './color' +import { ColorData } from './data' -export { ElementColor } from './structure/element'; - -export function colorToRgb(hexColor: number) { - return { r: hexColor >> 16 & 255, g: hexColor >> 8 & 255, b: hexColor & 255 } -} - -/** Copies hex color to rgb array */ -export function colorToArray(hexColor: number, array: Helpers.NumberArray, offset: number) { - - array[ offset ] = (hexColor >> 16 & 255) - array[ offset + 1 ] = (hexColor >> 8 & 255) - array[ offset + 2 ] = (hexColor & 255) -} - -/** Copies normalized (0 to 1) hex color to rgb array */ -export function normalizedColorToArray(hexColor: number, array: Helpers.NumberArray, offset: number) { - array[ offset ] = (hexColor >> 16 & 255) / 255 - array[ offset + 1 ] = (hexColor >> 8 & 255) / 255 - array[ offset + 2 ] = (hexColor & 255) / 255 -} - -/** Linear interpolation between two colors */ -export function interpolate(c1: number, c2: number, t: number) { - const r1 = c1 >> 16 & 255 - const g1 = c1 >> 8 & 255 - const b1 = c1 & 255 - const r2 = c2 >> 16 & 255 - const g2 = c2 >> 8 & 255 - const b2 = c2 & 255 - - const r = r1 + (r2 - r1) * t - const g = g1 + (g2 - g1) * t - const b = b1 + (b2 - b1) * t - - return (r << 16) | (g << 8) | b -} - -export type Color = number - -export interface ColorScale { - /** Returns hex color for given value */ - color: (value: number) => Color - /** Copies color to rgb int8 array */ - colorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void - /** Copies normalized (0 to 1) hex color to rgb array */ - normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void -} - -export const DefaultColorScale = { - domain: [0, 1], - reverse: false, - colors: ColorBrewer.RdYlBu as Color[] -} -export type ColorScaleProps = Partial<typeof DefaultColorScale> - -export namespace ColorScale { - export function create(props: ColorScaleProps): ColorScale { - const { domain, reverse, colors } = { ...DefaultColorScale, ...props } - const [ min, max ] = reverse ? domain.slice().reverse() : domain - const count1 = colors.length - 1 - - function color(value: number) { - const t = ((value - min) / (max - min)) * count1 - const tf = Math.floor(t) - const c1 = colors[tf] - const c2 = colors[Math.ceil(t)] - return interpolate(c1, c2, t - tf) - } - return { - color, - colorToArray: (value: number, array: Helpers.NumberArray, offset: number) => { - colorToArray(color(value), array, offset) - }, - normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => { - normalizedColorToArray(color(value), array, offset) - }, - } - } -} +export { Color, ColorData } +export { ColorBrewer, ColorNames } from './tables' \ No newline at end of file diff --git a/src/mol-geo/color/scale.ts b/src/mol-geo/color/scale.ts new file mode 100644 index 0000000000000000000000000000000000000000..5673e1c94f35aaa92094e03c9ee6bbcc89846df4 --- /dev/null +++ b/src/mol-geo/color/scale.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Color from './color' +import { ColorBrewer } from './tables' + +export interface ColorScale { + /** Returns hex color for given value */ + color: (value: number) => Color + /** Copies color to rgb int8 array */ + colorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void + /** Copies normalized (0 to 1) hex color to rgb array */ + normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => void +} + +export const DefaultColorScale = { + domain: [0, 1], + reverse: false, + colors: ColorBrewer.RdYlBu as Color[] +} +export type ColorScaleProps = Partial<typeof DefaultColorScale> + +export namespace ColorScale { + export function create(props: ColorScaleProps): ColorScale { + const { domain, reverse, colors } = { ...DefaultColorScale, ...props } + const [ min, max ] = reverse ? domain.slice().reverse() : domain + const count1 = colors.length - 1 + + function color(value: number) { + const t = ((value - min) / (max - min)) * count1 + const tf = Math.floor(t) + const c1 = colors[tf] + const c2 = colors[Math.ceil(t)] + return Color.interpolate(c1, c2, t - tf) + } + return { + color, + colorToArray: (value: number, array: Helpers.NumberArray, offset: number) => { + Color.toArray(color(value), array, offset) + }, + normalizedColorToArray: (value: number, array: Helpers.NumberArray, offset: number) => { + Color.toArrayNormalized(color(value), array, offset) + }, + } + } +} diff --git a/src/mol-geo/color/structure/element.ts b/src/mol-geo/color/structure/element.ts index 32be722efb086fac16b25b2f3291d278d949bbf5..70574c14cbce7ee2d8ecf162b699fd4dbdca3ba7 100644 --- a/src/mol-geo/color/structure/element.ts +++ b/src/mol-geo/color/structure/element.ts @@ -5,15 +5,33 @@ */ import { ElementSymbol } from 'mol-model/structure/model/types'; +import Color from '../color'; +import { createAttributeOrElementColor } from '../data'; +import { StructureColorDataProps } from '.'; +import { OrderedSet } from 'mol-data/int'; // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF) -export const ElementColors: { [k: string]: number } = { +export const ElementSymbolColors: { [k: string]: Color } = { 'H': 0xFFFFFF, 'HE': 0xD9FFFF, 'LI': 0xCC80FF, 'BE': 0xC2FF00, 'B': 0xFFB5B5, 'C': 0x909090, 'N': 0x3050F8, 'O': 0xFF0D0D, 'F': 0x90E050, 'NE': 0xB3E3F5, 'NA': 0xAB5CF2, 'MG': 0x8AFF00, 'AL': 0xBFA6A6, 'SI': 0xF0C8A0, 'P': 0xFF8000, 'S': 0xFFFF30, 'CL': 0x1FF01F, 'AR': 0x80D1E3, 'K': 0x8F40D4, 'CA': 0x3DFF00, 'SC': 0xE6E6E6, 'TI': 0xBFC2C7, 'V': 0xA6A6AB, 'CR': 0x8A99C7, 'MN': 0x9C7AC7, 'FE': 0xE06633, 'CO': 0xF090A0, 'NI': 0x50D050, 'CU': 0xC88033, 'ZN': 0x7D80B0, 'GA': 0xC28F8F, 'GE': 0x668F8F, 'AS': 0xBD80E3, 'SE': 0xFFA100, 'BR': 0xA62929, 'KR': 0x5CB8D1, 'RB': 0x702EB0, 'SR': 0x00FF00, 'Y': 0x94FFFF, 'ZR': 0x94E0E0, 'NB': 0x73C2C9, 'MO': 0x54B5B5, 'TC': 0x3B9E9E, 'RU': 0x248F8F, 'RH': 0x0A7D8C, 'PD': 0x006985, 'AG': 0xC0C0C0, 'CD': 0xFFD98F, 'IN': 0xA67573, 'SN': 0x668080, 'SB': 0x9E63B5, 'TE': 0xD47A00, 'I': 0x940094, 'XE': 0x940094, 'CS': 0x57178F, 'BA': 0x00C900, 'LA': 0x70D4FF, 'CE': 0xFFFFC7, 'PR': 0xD9FFC7, 'ND': 0xC7FFC7, 'PM': 0xA3FFC7, 'SM': 0x8FFFC7, 'EU': 0x61FFC7, 'GD': 0x45FFC7, 'TB': 0x30FFC7, 'DY': 0x1FFFC7, 'HO': 0x00FF9C, 'ER': 0x00E675, 'TM': 0x00D452, 'YB': 0x00BF38, 'LU': 0x00AB24, 'HF': 0x4DC2FF, 'TA': 0x4DA6FF, 'W': 0x2194D6, 'RE': 0x267DAB, 'OS': 0x266696, 'IR': 0x175487, 'PT': 0xD0D0E0, 'AU': 0xFFD123, 'HG': 0xB8B8D0, 'TL': 0xA6544D, 'PB': 0x575961, 'BI': 0x9E4FB5, 'PO': 0xAB5C00, 'AT': 0x754F45, 'RN': 0x428296, 'FR': 0x420066, 'RA': 0x007D00, 'AC': 0x70ABFA, 'TH': 0x00BAFF, 'PA': 0x00A1FF, 'U': 0x008FFF, 'NP': 0x0080FF, 'PU': 0x006BFF, 'AM': 0x545CF2, 'CM': 0x785CE3, 'BK': 0x8A4FE3, 'CF': 0xA136D4, 'ES': 0xB31FD4, 'FM': 0xB31FBA, 'MD': 0xB30DA6, 'NO': 0xBD0D87, 'LR': 0xC70066, 'RF': 0xCC0059, 'DB': 0xD1004F, 'SG': 0xD90045, 'BH': 0xE00038, 'HS': 0xE6002E, 'MT': 0xEB0026, 'DS': 0xFFFFFF, 'RG': 0xFFFFFF, 'CN': 0xFFFFFF, 'UUT': 0xFFFFFF, 'FL': 0xFFFFFF, 'UUP': 0xFFFFFF, 'LV': 0xFFFFFF, 'UUH': 0xFFFFFF, 'D': 0xFFFFC0, 'T': 0xFFFFA0 } -const DefaultElementColor = 0xFFFFFF +const DefaultElementSymbolColor = 0xFFFFFF -export function ElementColor(element: ElementSymbol): number { - const c = ElementColors[element as any as string]; - return c === void 0 ? DefaultElementColor : c +export function elementSymbolColor(element: ElementSymbol): Color { + const c = ElementSymbolColors[element as any as string]; + return c === void 0 ? DefaultElementSymbolColor : c +} + +export function elementSymbolColorData(props: StructureColorDataProps) { + const { units, elementGroup, mesh } = props + const { type_symbol } = units[0].model.hierarchy.atoms + return createAttributeOrElementColor(mesh, { + colorFn: (elementIdx: number) => { + const e = OrderedSet.getAt(elementGroup.elements, elementIdx) + return elementSymbolColor(type_symbol.value(e)) + }, + vertexCount: mesh.vertexCount, + offsetCount: mesh.offsetCount, + offsets: mesh.offsetBuffer as any + }) } \ No newline at end of file diff --git a/src/mol-geo/color/structure/index.ts b/src/mol-geo/color/structure/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..71b59a49e20e30d2f107e0fac180bb156399890c --- /dev/null +++ b/src/mol-geo/color/structure/index.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ElementGroup, Unit } from 'mol-model/structure'; +import { Mesh } from '../../shape/mesh'; + +export interface StructureColorDataProps { + units: ReadonlyArray<Unit>, + elementGroup: ElementGroup, + mesh: Mesh +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 03cc5f995c07f7d23620927006ddd8f5fcc86a5d..ae64e3cc0f0763078f5eedf9e87a463caecce893 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -15,24 +15,16 @@ import { RepresentationProps, UnitsRepresentation } from './index'; import { Task } from 'mol-task' import { MeshBuilder } from '../../shape/mesh-builder'; import { VdwRadius } from 'mol-model/structure/model/properties/atomic'; -import { ElementColor, colorToArray, normalizedColorToArray, ColorScale } from '../../color'; -import { Color } from 'mol-gl/renderable/mesh'; -import { createColorTexture } from 'mol-gl/util'; +import { elementSymbolColorData } from '../../color/structure/element'; +import { ColorData } from '../../color'; +import { createInstanceColor, createUniformColor, createElementInstanceColor } from '../../color/data'; +import { ColorScale } from '../../color/scale'; export const DefaultSpacefillProps = { detail: 0 } export type SpacefillProps = Partial<typeof DefaultSpacefillProps> -// function buildColorBuffer() { -// if (props && props.color) { -// colors = new Float32Array(icosahedron.vertices.length) -// for (let i = 0, il = colors.length; i < il; i += 3) { -// hexColorToArray(props.color, colors, i) -// } -// } -// } - export default function Spacefill(): UnitsRepresentation<SpacefillProps> { const renderObjects: RenderObject[] = [] @@ -70,52 +62,37 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { // console.log(mesh) if (!mesh.offsetBuffer.ref.value) return - const unitsCount = units.length - const transformArray = new Float32Array(unitsCount * 16) - for (let i = 0; i < unitsCount; i++) { + const unitCount = units.length + const transformArray = new Float32Array(unitCount * 16) + for (let i = 0; i < unitCount; i++) { Mat4.toArray(units[i].operator.matrix, transformArray, i * 16) } - // console.log({ unitsCount, elementCount }) - - let colorType = 'element' - let color: Color - - if (colorType === 'attribute') { - const colors = new Float32Array(mesh.vertexCount * 3); - const offsets = mesh.offsetBuffer.ref.value - for (let i = 0, il = mesh.offsetCount - 1; i < il; ++i) { - const start = offsets[i] - const end = offsets[i + 1] - const e = OrderedSet.getAt(elementGroup.elements, i) - const hexColor = ElementColor(type_symbol.value(e)) - for (let i = start, il = end; i < il; ++i) { - normalizedColorToArray(hexColor, colors, i * 3) - } - } - color = { type: 'attribute', value: ValueCell.create(colors) } + // console.log({ unitCount, elementCount }) + + let colorType = 'element-instance' + let color: ColorData + + if (colorType === 'uniform') { + color = createUniformColor({ value: 0xFF4411 }) + } else if (colorType === 'attribute') { + color = elementSymbolColorData({ units, elementGroup, mesh }) } else if (colorType === 'instance') { - const colors = createColorTexture(unitsCount) - const scale = ColorScale.create({ domain: [ 0, unitsCount - 1 ] }) - for (let i = 0; i < unitsCount; i++) { - scale.colorToArray(i, colors, i * 3) - } - color = { type: 'instance', value: ValueCell.create(colors) } - } else if (colorType === 'element') { - const elementCount = mesh.offsetCount - 1 - const count = unitsCount * elementCount - const colors = createColorTexture(count) - const scale = ColorScale.create({ domain: [ 0, count - 1 ] }) - let colorOffset = 0 - for (let i = 0; i < unitsCount; i++) { - for (let j = 0, jl = elementCount; j < jl; ++j) { - const hexColor = scale.color(i * elementCount + j) - colorToArray(hexColor, colors, colorOffset) - colorOffset += 3 - } - } - color = { type: 'element', value: ValueCell.create(colors) } + const scale = ColorScale.create({ domain: [ 0, unitCount - 1 ] }) + color = createInstanceColor({ + colorFn: scale.color, + unitCount + }) + } else if (colorType === 'element-instance') { + const scale = ColorScale.create({ domain: [ 0, unitCount * elementCount - 1 ] }) + color = createElementInstanceColor({ + colorFn: (unitIdx, elementIdx) => scale.color(unitIdx * elementCount + elementIdx), + unitCount, + offsetCount: mesh.offsetCount, + offsets: mesh.offsetBuffer as any + }) } + console.log(color!) const spheres = createRenderObject('mesh', { position: mesh.vertexBuffer, @@ -125,7 +102,7 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { transform: ValueCell.create(transformArray), index: mesh.indexBuffer, - instanceCount: unitsCount, + instanceCount: unitCount, indexCount: mesh.triangleCount, elementCount: mesh.offsetCount - 1, positionCount: mesh.vertexCount diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 630a470c854933646959c815608de8b33f6dd35f..8d74b1032c401108a81c0f444662618f8b2d283d 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -6,21 +6,28 @@ import REGL = require('regl'); import { ValueCell } from 'mol-util/value-cell' +import { ColorData } from 'mol-geo/color'; import { Renderable } from '../renderable' -import { ColorTexture } from '../util' import { getBuffers, createTransformAttributes, fillSerial, createColorUniforms } from './util' import Attribute from '../attribute'; -import { MeshShaders } from '../shaders' +import { MeshShaders, addDefines, ShaderDefines } from '../shaders' type Mesh = 'mesh' type Uniforms = { [k: string]: REGL.Uniform | REGL.Texture } -type AttributeColor = { type: 'attribute', value: ValueCell<Float32Array> } -type InstanceColor = { type: 'instance', value: ValueCell<ColorTexture> } -type ElementColor = { type: 'element', value: ValueCell<ColorTexture> } -export type Color = AttributeColor | InstanceColor | ElementColor +function getColorDefines(color: ColorData) { + const defines: ShaderDefines = {} + switch (color.type) { + case 'uniform': defines.UNIFORM_COLOR = ''; break; + case 'attribute': defines.ATTRIBUTE_COLOR = ''; break; + case 'element': defines.ELEMENT_COLOR = ''; break; + case 'instance': defines.INSTANCE_COLOR = ''; break; + case 'element-instance': defines.ELEMENT_INSTANCE_COLOR = ''; break; + } + return defines +} namespace Mesh { export type Data = { @@ -28,7 +35,7 @@ namespace Mesh { normal: ValueCell<Float32Array> id: ValueCell<Float32Array> - readonly color: Color + readonly color: ColorData transform: ValueCell<Float32Array> index: ValueCell<Uint32Array> @@ -39,6 +46,7 @@ namespace Mesh { } export function create(regl: REGL.Regl, data: Data, _uniforms: Uniforms): Renderable { + const defines = getColorDefines(data.color) const instanceId = ValueCell.create(fillSerial(new Float32Array(data.instanceCount))) const uniforms = { objectId: _uniforms.objectId || 0, @@ -46,8 +54,10 @@ namespace Mesh { elementCount: data.elementCount, ..._uniforms } - if (data.color.type === 'instance' || data.color.type === 'element') { - Object.assign(uniforms, createColorUniforms(regl, data.color.value as ValueCell<ColorTexture>)) + if (data.color.type === 'instance' || data.color.type === 'element' || data.color.type === 'element-instance') { + Object.assign(uniforms, createColorUniforms(regl, data.color.value)) + } else if (data.color.type === 'uniform') { + Object.assign(uniforms, { color: data.color.value }) } const attributes = getBuffers({ instanceId: Attribute.create(regl, instanceId, data.instanceCount, { size: 1, divisor: 1 }), @@ -58,10 +68,10 @@ namespace Mesh { ...createTransformAttributes(regl, data.transform, data.instanceCount) }) if (data.color.type === 'attribute') { - attributes.color = Attribute.create(regl, data.color.value as ValueCell<Float32Array>, data.positionCount, { size: 3 }).buffer + attributes.color = Attribute.create(regl, data.color.value, data.positionCount, { size: 3 }).buffer } const command = regl({ - ...MeshShaders, + ...addDefines(defines, MeshShaders), uniforms, attributes, elements: regl.elements({ diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index ee4ad0fabebaabd36d818a209debf1cfaef61666..d7f6d4cafd0b3e15dba88bc296425e8f54c7bc9f 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/src/mol-gl/shader/mesh.frag @@ -4,10 +4,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -// #define ATTRIBUTE_COLOR -// #define INSTANCE_COLOR -#define ELEMENT_COLOR - precision highp float; struct Light { @@ -23,36 +19,15 @@ uniform mat4 view; varying vec3 vNormal, vViewPosition; -#if defined( UNIFORM_COLOR ) +#if defined(UNIFORM_COLOR) uniform vec3 color; -#elif defined( ATTRIBUTE_COLOR ) || defined( INSTANCE_COLOR ) || defined( ELEMENT_COLOR ) +#elif defined(ATTRIBUTE_COLOR) || defined(INSTANCE_COLOR) || defined(ELEMENT_COLOR) || defined(ELEMENT_INSTANCE_COLOR) varying vec3 vColor; #endif -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) +#pragma glslify: attenuation = require(./util/attenuation.glsl) +#pragma glslify: calculateSpecular = require(./util/phong-specular.glsl) +#pragma glslify: calculateDiffuse = require(./util/oren-nayar-diffuse.glsl) const float specularScale = 0.65; const float shininess = 100.0; @@ -61,9 +36,9 @@ const float albedo = 0.95; void main() { // material color - #if defined( UNIFORM_COLOR ) + #if defined(UNIFORM_COLOR) vec3 material = color; - #elif defined( ATTRIBUTE_COLOR ) || defined( INSTANCE_COLOR ) || defined( ELEMENT_COLOR ) + #elif defined(ATTRIBUTE_COLOR) || defined(INSTANCE_COLOR) || defined(ELEMENT_COLOR) || defined(ELEMENT_INSTANCE_COLOR) vec3 material = vColor; #endif @@ -81,8 +56,8 @@ void main() { 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; + float specular = calculateSpecular(L, V, N, shininess) * specularScale * falloff; + vec3 diffuse = light.color * calculateDiffuse(L, V, N, roughness, albedo) * falloff; vec3 ambient = light.ambient; // add the lighting diff --git a/src/mol-gl/shader/mesh.vert b/src/mol-gl/shader/mesh.vert index 91b2c82de177b602dc183825e80bd208fd6c088a..d14e6e058d07b0e2bda7ad764eba628e93ac8302 100644 --- a/src/mol-gl/shader/mesh.vert +++ b/src/mol-gl/shader/mesh.vert @@ -4,10 +4,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -// #define ATTRIBUTE_COLOR -// #define INSTANCE_COLOR -#define ELEMENT_COLOR - precision highp float; uniform mat4 projection, model, view; @@ -16,11 +12,11 @@ uniform int objectId; uniform int instanceCount; uniform int elementCount; -#if defined( UNIFORM_COLOR ) +#if defined(UNIFORM_COLOR) uniform vec3 color; -#elif defined( ATTRIBUTE_COLOR ) +#elif defined(ATTRIBUTE_COLOR) attribute vec3 color; -#elif defined( INSTANCE_COLOR ) || defined( ELEMENT_COLOR ) +#elif defined(INSTANCE_COLOR) || defined(ELEMENT_COLOR) || defined(ELEMENT_INSTANCE_COLOR) uniform vec2 colorTexSize; uniform sampler2D colorTex; #endif @@ -35,16 +31,18 @@ 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) +#pragma glslify: inverse = require(./util/inverse.glsl) +#pragma glslify: read_vec3 = require(./util/read-vec3.glsl) +#pragma glslify: transpose = require(./util/transpose.glsl) void main(){ - #if defined( ATTRIBUTE_COLOR ) + #if defined(ATTRIBUTE_COLOR) vColor = color; - #elif defined( INSTANCE_COLOR ) + #elif defined(INSTANCE_COLOR) vColor = read_vec3(colorTex, instanceId, colorTexSize); - #elif defined( ELEMENT_COLOR ) + #elif defined(ELEMENT_COLOR) + vColor = read_vec3(colorTex, elementId, colorTexSize); + #elif defined(ELEMENT_INSTANCE_COLOR) vColor = read_vec3(colorTex, instanceId * float(elementCount) + elementId, colorTexSize); #endif diff --git a/src/mol-gl/shader/attenuation.glsl b/src/mol-gl/shader/util/attenuation.glsl similarity index 100% rename from src/mol-gl/shader/attenuation.glsl rename to src/mol-gl/shader/util/attenuation.glsl diff --git a/src/mol-gl/shader/inverse.glsl b/src/mol-gl/shader/util/inverse.glsl similarity index 100% rename from src/mol-gl/shader/inverse.glsl rename to src/mol-gl/shader/util/inverse.glsl diff --git a/src/mol-gl/shader/util/oren-nayar-diffuse.glsl b/src/mol-gl/shader/util/oren-nayar-diffuse.glsl new file mode 100644 index 0000000000000000000000000000000000000000..8124048963d1fa53c7827cc780d650be3cb76d29 --- /dev/null +++ b/src/mol-gl/shader/util/oren-nayar-diffuse.glsl @@ -0,0 +1,21 @@ +// (c) 2014 Mikola Lysenko. MIT License +// https://github.com/glslify/glsl-diffuse-oren-nayar + +#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: export(orenNayarDiffuse) \ No newline at end of file diff --git a/src/mol-gl/shader/util/phong-specular.glsl b/src/mol-gl/shader/util/phong-specular.glsl new file mode 100644 index 0000000000000000000000000000000000000000..a4cd935072d0ad0f1ae2fad2a6ea490e628f4e83 --- /dev/null +++ b/src/mol-gl/shader/util/phong-specular.glsl @@ -0,0 +1,10 @@ +// (c) 2014 Mikola Lysenko. MIT License +// https://github.com/glslify/glsl-specular-phong + +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); +} + +#pragma glslify: export(phongSpecular) \ No newline at end of file diff --git a/src/mol-gl/shader/read-vec3.glsl b/src/mol-gl/shader/util/read-vec3.glsl similarity index 100% rename from src/mol-gl/shader/read-vec3.glsl rename to src/mol-gl/shader/util/read-vec3.glsl diff --git a/src/mol-gl/shader/transpose.glsl b/src/mol-gl/shader/util/transpose.glsl similarity index 100% rename from src/mol-gl/shader/transpose.glsl rename to src/mol-gl/shader/util/transpose.glsl diff --git a/src/mol-gl/shaders.ts b/src/mol-gl/shaders.ts index 51ab7832064e6d122c52f7d80ec5006e908a3adf..5e3937fe5c48d57262c6307d8238faf9982882f1 100644 --- a/src/mol-gl/shaders.ts +++ b/src/mol-gl/shaders.ts @@ -1,12 +1,50 @@ -const PointShaders = { +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +interface Shaders { + vert: string + frag: string +} + +export const PointShaders = { vert: require('mol-gl/shader/point.vert'), frag: require('mol-gl/shader/point.frag') } -const MeshShaders = { +export 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 +type ShaderDefine = ( + 'UNIFORM_COLOR' | 'ATTRIBUTE_COLOR' | 'INSTANCE_COLOR' | 'ELEMENT_COLOR' | 'ELEMENT_INSTANCE_COLOR' +) +export type ShaderDefines = { + [k in ShaderDefine]?: number|string +} + +function getDefines (defines: ShaderDefines) { + if (defines === undefined) return '' + const lines = [] + for (const name in defines) { + const value = defines[ name as keyof ShaderDefines ] + if (value) { + lines.push(`#define ${name} ${value}`) + } else { + lines.push(`#define ${name}`) + } + } + return lines.join('\n') + '\n' +} + +export function addDefines(defines: ShaderDefines, shaders: Shaders) { + const header = getDefines(defines) + return { + vert: `${header}${shaders.vert}`, + frag: `${header}${shaders.frag}` + } +} \ No newline at end of file diff --git a/src/mol-gl/util.ts b/src/mol-gl/util.ts deleted file mode 100644 index 5ce7ca89e4f157caa2c9b50e77899f0f9b5ea9f7..0000000000000000000000000000000000000000 --- a/src/mol-gl/util.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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 } -} - -export interface ColorTexture extends Uint8Array { - width: number, - height: number -} - -export function createColorTexture (n: number): ColorTexture { - const colorTexInfo = calculateTextureInfo(n, 3) - const colorTexture = new Uint8Array(colorTexInfo.length) - return Object.assign(colorTexture, { - width: colorTexInfo.width, - height: colorTexInfo.height - }) -} \ No newline at end of file