diff --git a/src/mol-geo/geometry/text/text-builder.ts b/src/mol-geo/geometry/text/text-builder.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd7c381373c94a70197da94f2020aa75f489eb1a --- /dev/null +++ b/src/mol-geo/geometry/text/text-builder.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { ValueCell } from 'mol-util/value-cell' +import { ChunkedArray } from 'mol-data/util'; +import { Text } from './text'; +import { getFontAtlas } from './font-atlas'; + +const quadIndices = new Uint16Array([ + 0, 1, 2, + 1, 3, 2 +]) + +export interface TextBuilder { + add(str: string, x: number, y: number, z: number, group: number): void + getText(): Text +} + +export namespace TextBuilder { + export function create(props: Partial<PD.Values<Text.Params>> = {}, initialCount = 2048, chunkSize = 1024, text?: Text): TextBuilder { + const centers = ChunkedArray.create(Float32Array, 3, chunkSize, text ? text.centerBuffer.ref.value : initialCount); + const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, text ? text.mappingBuffer.ref.value : initialCount); + const indices = ChunkedArray.create(Uint32Array, 3, chunkSize, text ? text.indexBuffer.ref.value : initialCount); + const groups = ChunkedArray.create(Float32Array, 1, chunkSize, text ? text.groupBuffer.ref.value : initialCount); + const tcoords = ChunkedArray.create(Float32Array, 2, chunkSize, text ? text.tcoordBuffer.ref.value : initialCount); + + const p = { ...PD.getDefaultValues(Text.Params), ...props } + const { attachment, background, backgroundMargin } = p + + const fontAtlas = getFontAtlas(p) + const { lineHeight } = fontAtlas + + const margin = (lineHeight * backgroundMargin * 0.1) - 10 + const outline = fontAtlas.buffer + console.log('margin', margin) + + return { + add: (str: string, x: number, y: number, z: number, group: number) => { + let xadvance = 0 + const nChar = str.length + + // calculate width + for (let iChar = 0; iChar < nChar; ++iChar) { + const c = fontAtlas.get(str[iChar]) + xadvance += c.w - 2 * outline + } + + // attachment + let yShift: number, xShift: number + if (attachment.startsWith('top')) { + yShift = lineHeight / 1.25 + } else if (attachment.startsWith('middle')) { + yShift = lineHeight / 2.5 + } else { + yShift = 0 // "bottom" + } + if (attachment.endsWith('right')) { + xShift = xadvance + } else if (attachment.endsWith('center')) { + xShift = xadvance / 2 + } else { + xShift = 0 // "left" + } + xShift += outline + yShift += outline + + // background + if (background) { + ChunkedArray.add2(mappings, -lineHeight / 6 - xShift - margin, lineHeight - yShift + margin) + ChunkedArray.add2(mappings, -lineHeight / 6 - xShift - margin, 0 - yShift - margin) + ChunkedArray.add2(mappings, xadvance + lineHeight / 6 - xShift + 2 * outline + margin, lineHeight - yShift + margin) + ChunkedArray.add2(mappings, xadvance + lineHeight / 6 - xShift + 2 * outline + margin, 0 - yShift - margin) + + const offset = centers.elementCount + for (let i = 0; i < 4; ++i) { + ChunkedArray.add2(tcoords, 0, 10) + ChunkedArray.add3(centers, x, y, z); + ChunkedArray.add(groups, group); + } + ChunkedArray.add3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]) + ChunkedArray.add3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]) + } + + xadvance = 0 + + for (let iChar = 0; iChar < nChar; ++iChar) { + const c = fontAtlas.get(str[iChar]) + + ChunkedArray.add2(mappings, xadvance - xShift, c.h - yShift) // top left + ChunkedArray.add2(mappings, xadvance - xShift, 0 - yShift) // bottom left + ChunkedArray.add2(mappings, xadvance + c.w - xShift, c.h - yShift) // top right + ChunkedArray.add2(mappings, xadvance + c.w - xShift, 0 - yShift) // bottom right + + const texWidth = fontAtlas.texture.width + const texHeight = fontAtlas.texture.height + + ChunkedArray.add2(tcoords, c.x / texWidth, c.y / texHeight) // top left + ChunkedArray.add2(tcoords, c.x / texWidth, (c.y + c.h) / texHeight) // bottom left + ChunkedArray.add2(tcoords, (c.x + c.w) / texWidth, c.y / texHeight) // top right + ChunkedArray.add2(tcoords, (c.x + c.w) / texWidth, (c.y + c.h) / texHeight) // bottom right + + xadvance += c.w - 2 * outline + + const offset = centers.elementCount + for (let i = 0; i < 4; ++i) { + ChunkedArray.add3(centers, x, y, z); + ChunkedArray.add(groups, group); + } + ChunkedArray.add3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]) + ChunkedArray.add3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]) + } + }, + getText: () => { + const cb = ChunkedArray.compact(centers, true) as Float32Array + const mb = ChunkedArray.compact(mappings, true) as Float32Array + const ib = ChunkedArray.compact(indices, true) as Uint32Array + const gb = ChunkedArray.compact(groups, true) as Float32Array + const tb = ChunkedArray.compact(tcoords, true) as Float32Array + return { + kind: 'text', + charCount: centers.elementCount / 4, + fontAtlas, + centerBuffer: text ? ValueCell.update(text.centerBuffer, cb) : ValueCell.create(cb), + mappingBuffer: text ? ValueCell.update(text.centerBuffer, mb) : ValueCell.create(mb), + indexBuffer: text ? ValueCell.update(text.indexBuffer, ib) : ValueCell.create(ib), + groupBuffer: text ? ValueCell.update(text.groupBuffer, gb) : ValueCell.create(gb), + tcoordBuffer: text ? ValueCell.update(text.tcoordBuffer, tb) : ValueCell.create(tb), + } + } + } + } +} \ No newline at end of file diff --git a/src/mol-geo/geometry/text/text.ts b/src/mol-geo/geometry/text/text.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1c4d3f0bfc74858cabed410cdfb35742a3ad76e --- /dev/null +++ b/src/mol-geo/geometry/text/text.ts @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { ValueCell } from 'mol-util'; +import { Geometry } from '../geometry'; +import { LocationIterator } from 'mol-geo/util/location-iterator'; +import { TransformData, createIdentityTransform } from '../transform-data'; +import { Theme } from 'mol-theme/theme'; +import { createColors } from '../color-data'; +import { createSizes, getMaxSize } from '../size-data'; +import { createMarkers } from '../marker-data'; +import { ColorNames } from 'mol-util/color/tables'; +import { NullLocation } from 'mol-model/location'; +import { UniformColorTheme } from 'mol-theme/color/uniform'; +import { UniformSizeTheme } from 'mol-theme/size/uniform'; +import { Sphere3D } from 'mol-math/geometry'; +import { calculateBoundingSphere } from 'mol-gl/renderable/util'; +import { TextValues } from 'mol-gl/renderable/text'; +import { Color } from 'mol-util/color'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { FontAtlas, getFontAtlas, FontAtlasParams } from './font-atlas'; +import { RenderableState } from 'mol-gl/renderable'; +import { clamp } from 'mol-math/interpolate'; + +type TextAttachment = 'bottom-left' | 'bottom-center' | 'bottom-right' | 'middle-left' | 'middle-center' | 'middle-right' | 'top-left' | 'top-center' | 'top-right' + +/** Text */ +export interface Text { + readonly kind: 'text', + + /** Number of characters in the text */ + readonly charCount: number, + /** Font Atlas */ + readonly fontAtlas: FontAtlas, + + /** Center buffer as array of xyz values wrapped in a value cell */ + readonly centerBuffer: ValueCell<Float32Array>, + /** Mapping buffer as array of xy values wrapped in a value cell */ + readonly mappingBuffer: ValueCell<Float32Array>, + /** Index buffer as array of center index triplets wrapped in a value cell */ + readonly indexBuffer: ValueCell<Uint32Array>, + /** Group buffer as array of group ids for each vertex wrapped in a value cell */ + readonly groupBuffer: ValueCell<Float32Array>, + /** Texture coordinates buffer as array of uv values wrapped in a value cell */ + readonly tcoordBuffer: ValueCell<Float32Array>, +} + +export namespace Text { + export function createEmpty(text?: Text): Text { + const cb = text ? text.centerBuffer.ref.value : new Float32Array(0) + const mb = text ? text.mappingBuffer.ref.value : new Float32Array(0) + const ib = text ? text.indexBuffer.ref.value : new Uint32Array(0) + const gb = text ? text.groupBuffer.ref.value : new Float32Array(0) + const tb = text ? text.tcoordBuffer.ref.value : new Float32Array(0) + return { + kind: 'text', + charCount: 0, + fontAtlas: getFontAtlas({}), + centerBuffer: text ? ValueCell.update(text.centerBuffer, cb) : ValueCell.create(cb), + mappingBuffer: text ? ValueCell.update(text.mappingBuffer, mb) : ValueCell.create(mb), + indexBuffer: text ? ValueCell.update(text.indexBuffer, ib) : ValueCell.create(ib), + groupBuffer: text ? ValueCell.update(text.groupBuffer, gb) : ValueCell.create(gb), + tcoordBuffer: text ? ValueCell.update(text.tcoordBuffer, tb) : ValueCell.create(tb) + } + } + + export const Params = { + ...Geometry.Params, + ...FontAtlasParams, + sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), + + borderWidth: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), + borderColor: PD.Color(ColorNames.grey), + offsetX: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), + offsetY: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), + offsetZ: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }), + background: PD.Boolean(false), + backgroundMargin: PD.Numeric(0.2, { min: 0, max: 10, step: 0.1 }), + backgroundColor: PD.Color(ColorNames.grey), + backgroundOpacity: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), + + attachment: PD.Select('normal', [['bottom-left', 'bottom-left'], ['bottom-center', 'bottom-center'], ['bottom-right', 'bottom-right'], ['middle-left', 'middle-left'], ['top-left', 'top-left'], ['top-center', 'top-center'], ['top-right', 'top-right']] as [TextAttachment, string][]), + } + export type Params = typeof Params + + export function createValues(text: Text, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): TextValues { + const { instanceCount, groupCount } = locationIt + if (instanceCount !== transform.instanceCount.ref.value) { + throw new Error('instanceCount values in TransformData and LocationIterator differ') + } + + const color = createColors(locationIt, theme.color) + const size = createSizes(locationIt, theme.size) + const marker = createMarkers(instanceCount * groupCount) + + const counts = { drawCount: text.charCount * 2 * 3, groupCount, instanceCount } + + const padding = getMaxSize(size) + const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere( + text.centerBuffer.ref.value, text.charCount * 4, + transform.aTransform.ref.value, instanceCount, padding + ) + + console.log(props.sizeFactor, text.fontAtlas.lineHeight, props.fontSize) + + return { + aPosition: text.centerBuffer, + aMapping: text.mappingBuffer, + aGroup: text.groupBuffer, + elements: text.indexBuffer, + boundingSphere: ValueCell.create(boundingSphere), + invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), + ...color, + ...size, + ...marker, + ...transform, + + aTexCoord: text.tcoordBuffer, + tFont: ValueCell.create(text.fontAtlas.texture), + padding: ValueCell.create(padding), + + ...Geometry.createValues(props, counts), + uSizeFactor: ValueCell.create(props.sizeFactor / text.fontAtlas.lineHeight), + + uBorderWidth: ValueCell.create(clamp(props.borderWidth / 2, 0, 0.5)), + uBorderColor: ValueCell.create(Color.toArrayNormalized(props.borderColor, Vec3.zero(), 0)), + uOffsetX: ValueCell.create(props.offsetX), + uOffsetY: ValueCell.create(props.offsetY), + uOffsetZ: ValueCell.create(props.offsetZ), + uBackgroundColor: ValueCell.create(Color.toArrayNormalized(props.backgroundColor, Vec3.zero(), 0)), + uBackgroundOpacity: ValueCell.create(props.backgroundOpacity), + } + } + + export function createValuesSimple(text: Text, props: Partial<PD.Values<Params>>, colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData): TextValues { + + if (!transform) transform = createIdentityTransform() + const locationIterator = LocationIterator(1, transform.instanceCount.ref.value, () => NullLocation, false, () => false) + const theme: Theme = { + color: UniformColorTheme({}, { value: colorValue}), + size: UniformSizeTheme({}, { value: sizeValue}) + } + const p = { ...PD.getDefaultValues(Params), ...props } + + return createValues(text, transform, locationIterator, theme, p) + } + + export function updateValues(values: TextValues, props: PD.Values<Params>) { + Geometry.updateValues(values, props) + ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor) + } + + export function updateBoundingSphere(values: TextValues, text: Text) { + const padding = getMaxSize(values) + const { boundingSphere, invariantBoundingSphere } = calculateBoundingSphere( + values.aPosition.ref.value, text.charCount * 4, + values.aTransform.ref.value, values.instanceCount.ref.value, padding + ) + if (!Sphere3D.equals(boundingSphere, values.boundingSphere.ref.value)) { + ValueCell.update(values.boundingSphere, boundingSphere) + } + if (!Sphere3D.equals(invariantBoundingSphere, values.invariantBoundingSphere.ref.value)) { + ValueCell.update(values.invariantBoundingSphere, invariantBoundingSphere) + } + } + + export function createRenderableState(props: PD.Values<Params>): RenderableState { + const state = Geometry.createRenderableState(props) + updateRenderableState(state, props) + return state + } + + export function updateRenderableState(state: RenderableState, props: PD.Values<Params>) { + Geometry.updateRenderableState(state, props) + state.opaque = false + } +} \ No newline at end of file diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts index 871d7f07f30bf013268317b3d7b242e3a37dd022..19c05d0e7410b1b198c7f6a8ba7cc9f6792dd8b1 100644 --- a/src/mol-gl/render-object.ts +++ b/src/mol-gl/render-object.ts @@ -14,6 +14,7 @@ import { MeshValues, MeshRenderable } from './renderable/mesh'; import { PointsValues, PointsRenderable } from './renderable/points'; import { LinesValues, LinesRenderable } from './renderable/lines'; import { SpheresValues, SpheresRenderable } from './renderable/spheres'; +import { TextValues, TextRenderable } from './renderable/text'; const getNextId = idFactory(0, 0x7FFFFFFF) @@ -21,6 +22,7 @@ export interface BaseRenderObject { id: number, type: string, values: Renderable export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', values: MeshValues } export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues } export interface SpheresRenderObject extends BaseRenderObject { type: 'spheres', values: SpheresValues } +export interface TextRenderObject extends BaseRenderObject { type: 'text', values: TextValues } export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues } export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolumeValues } @@ -28,7 +30,7 @@ export interface GaussianDensityRenderObject extends BaseRenderObject { type: 'g // -export type GraphicsRenderObject = MeshRenderObject | PointsRenderObject | SpheresRenderObject | LinesRenderObject | DirectVolumeRenderObject +export type GraphicsRenderObject = MeshRenderObject | PointsRenderObject | SpheresRenderObject | TextRenderObject | LinesRenderObject | DirectVolumeRenderObject export type ComputeRenderObject = GaussianDensityRenderObject @@ -45,6 +47,9 @@ export function createPointsRenderObject(values: PointsValues, state: Renderable export function createSpheresRenderObject(values: SpheresValues, state: RenderableState): SpheresRenderObject { return { id: getNextId(), type: 'spheres', values, state } } +export function createTextRenderObject(values: TextValues, state: RenderableState): TextRenderObject { + return { id: getNextId(), type: 'text', values, state } +} export function createLinesRenderObject(values: LinesValues, state: RenderableState): LinesRenderObject { return { id: getNextId(), type: 'lines', values, state } } @@ -61,6 +66,7 @@ export function createRenderable(ctx: WebGLContext, o: RenderObject): Renderable case 'mesh': return MeshRenderable(ctx, o.id, o.values, o.state) case 'points': return PointsRenderable(ctx, o.id, o.values, o.state) case 'spheres': return SpheresRenderable(ctx, o.id, o.values, o.state) + case 'text': return TextRenderable(ctx, o.id, o.values, o.state) case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state) case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values, o.state) diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index bf9533943517af14b91f3c3e4bbd3f4c8a8124b7..bfcc32ca8a5003a773e0957379e07fe467555e48 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -148,6 +148,7 @@ export const GlobalUniformSchema = { uViewportHeight: UniformSpec('f'), uViewport: UniformSpec('v4'), + uCameraPosition: UniformSpec('v3'), uFogNear: UniformSpec('f'), uFogFar: UniformSpec('f'), uFogColor: UniformSpec('v3'), diff --git a/src/mol-gl/renderable/text.ts b/src/mol-gl/renderable/text.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e633f37d8ae6e8b832d6e87a2068cd1dee25cf3 --- /dev/null +++ b/src/mol-gl/renderable/text.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Renderable, RenderableState, createRenderable } from '../renderable' +import { WebGLContext } from '../webgl/context'; +import { createRenderItem } from '../webgl/render-item'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, Values, InternalSchema, SizeSchema, InternalValues, TextureSpec, ElementsSpec, ValueSpec } from './schema'; +import { TextShaderCode } from '../shader-code'; +import { ValueCell } from 'mol-util'; + +export const TextSchema = { + ...BaseSchema, + ...SizeSchema, + aPosition: AttributeSpec('float32', 3, 0), + aMapping: AttributeSpec('float32', 2, 0), + elements: ElementsSpec('uint32'), + + aTexCoord: AttributeSpec('float32', 2, 0), + tFont: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'), + padding: ValueSpec('number'), + + uBorderWidth: UniformSpec('f'), + uBorderColor: UniformSpec('v3'), + uOffsetX: UniformSpec('f'), + uOffsetY: UniformSpec('f'), + uOffsetZ: UniformSpec('f'), + uBackgroundColor: UniformSpec('v3'), + uBackgroundOpacity: UniformSpec('f'), +} +export type TextSchema = typeof TextSchema +export type TextValues = Values<TextSchema> + +export function TextRenderable(ctx: WebGLContext, id: number, values: TextValues, state: RenderableState): Renderable<TextValues> { + const schema = { ...GlobalUniformSchema, ...InternalSchema, ...TextSchema } + const internalValues: InternalValues = { + uObjectId: ValueCell.create(id), + uPickable: ValueCell.create(state.pickable ? 1 : 0) + } + const shaderCode = TextShaderCode + const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) + return createRenderable(renderItem, values, state); +} \ No newline at end of file diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 2c52cfc56844c3617533da5b6e72657a91ed9369..892ba6bc16c4fbf7a45c7973d5bf4ff1ac0eaeb5 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -99,6 +99,7 @@ namespace Renderer { uLightColor: ValueCell.create(lightColor), uLightAmbient: ValueCell.create(lightAmbient), + uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)), uFogNear: ValueCell.create(camera.state.fogNear), uFogFar: ValueCell.create(camera.state.fogFar), uFogColor: ValueCell.create(fogColor), @@ -160,6 +161,7 @@ namespace Renderer { ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection)) ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)) + ValueCell.update(globalUniforms.uCameraPosition, camera.state.position) ValueCell.update(globalUniforms.uFogFar, camera.state.fogFar) ValueCell.update(globalUniforms.uFogNear, camera.state.fogNear) diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 539bf98c98e6ed295183b94c7ed9c4940d12b1ef..48e635d1271d17a18261167443f74c16b6e26195 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -42,6 +42,12 @@ export const SpheresShaderCode = ShaderCode( { standardDerivatives: false, fragDepth: true } ) +export const TextShaderCode = ShaderCode( + require('mol-gl/shader/text.vert'), + require('mol-gl/shader/text.frag'), + { standardDerivatives: true, fragDepth: false } +) + export const LinesShaderCode = ShaderCode( require('mol-gl/shader/lines.vert'), require('mol-gl/shader/lines.frag'), diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl b/src/mol-gl/shader/chunks/common-vert-params.glsl index e2b15799cbbdc638f2796e680838ffdcd24a566f..390f0f1c6b22288090a448f1512bf27e95f650c6 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl @@ -1,4 +1,5 @@ uniform mat4 uProjection, uModel, uView; +uniform vec3 uCameraPosition; uniform int uObjectId; uniform int uInstanceCount; diff --git a/src/mol-gl/shader/text.frag b/src/mol-gl/shader/text.frag new file mode 100644 index 0000000000000000000000000000000000000000..567ef0fbd75dcf08bf7b08f5c8b44ca83bbcccf7 --- /dev/null +++ b/src/mol-gl/shader/text.frag @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +precision highp float; +precision highp int; + +#pragma glslify: import('./chunks/common-frag-params.glsl') +#pragma glslify: import('./chunks/color-frag-params.glsl') + +uniform sampler2D tFont; + +uniform vec3 uBorderColor; +uniform float uBorderWidth; +uniform vec3 uBackgroundColor; +uniform float uBackgroundOpacity; + +varying vec2 vTexCoord; + +const float smoothness = 32.0; +const float gamma = 2.2; + +void main2(){ + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); +} + +void main(){ + #pragma glslify: import('./chunks/assign-material-color.glsl') + + if (vTexCoord.x > 1.0) { + gl_FragColor = vec4(uBackgroundColor, uBackgroundOpacity); + } else { + // TODO nicer border + + // retrieve signed distance + float sdf = texture2D(tFont, vTexCoord).a + uBorderWidth; + + // perform adaptive anti-aliasing of the edges + float w = clamp( + smoothness * (abs(dFdx(vTexCoord.x)) + abs(dFdy(vTexCoord.y))), + 0.0, + 0.5 + ); + float a = smoothstep(0.5 - w, 0.5 + w, sdf); + + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.5) discard; + material.a *= a; + + if (uBorderWidth > 0.0 && sdf < (0.5 + uBorderWidth)) { + material.xyz = uBorderColor; + } + + gl_FragColor = material; + } + + #if defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking) + if (uAlpha < uPickingAlphaThreshold) + discard; // ignore so the element below can be picked + #else + #pragma glslify: import('./chunks/apply-marker-color.glsl') + #pragma glslify: import('./chunks/apply-fog.glsl') + #endif +} \ No newline at end of file diff --git a/src/mol-gl/shader/text.vert b/src/mol-gl/shader/text.vert new file mode 100644 index 0000000000000000000000000000000000000000..ec0a0a004953ad92994f5eac65e21069e99629a0 --- /dev/null +++ b/src/mol-gl/shader/text.vert @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +precision highp float; +precision highp int; + +#pragma glslify: import('./chunks/common-vert-params.glsl') +#pragma glslify: import('./chunks/color-vert-params.glsl') +#pragma glslify: import('./chunks/size-vert-params.glsl') + +uniform mat4 uModelView; + +attribute vec3 aPosition; +attribute vec2 aMapping; +attribute vec2 aTexCoord; +attribute mat4 aTransform; +attribute float aInstance; +attribute float aGroup; + +uniform float uOffsetX; +uniform float uOffsetY; +uniform float uOffsetZ; + +// uniform bool ortho; +uniform float uPixelRatio; +uniform float uViewportHeight; + +varying vec2 vTexCoord; + +#pragma glslify: matrixScale = require(./utils/matrix-scale.glsl) + +void main(void){ + #pragma glslify: import('./chunks/assign-color-varying.glsl') + #pragma glslify: import('./chunks/assign-marker-varying.glsl') + #pragma glslify: import('./chunks/assign-size.glsl') + + vTexCoord = aTexCoord; + + float scale = matrixScale(uModelView); + + float offsetX = uOffsetX * scale; + float offsetY = uOffsetY * scale; + float offsetZ = uOffsetZ * scale; + if (vTexCoord.x == 10.0) { + offsetZ -= 0.001; + } + + vec4 mvPosition = uModelView * aTransform * vec4(aPosition, 1.0); + + // #ifdef FIXED_SIZE + // if (ortho) { + // scale /= pixelRatio * ((uViewportHeight / 2.0) / -uCameraPosition.z) * 0.1; + // } else { + // scale /= pixelRatio * ((uViewportHeight / 2.0) / -mvPosition.z) * 0.1; + // } + // #endif + + vec4 mvCorner = vec4(mvPosition.xyz, 1.0); + mvCorner.xy += aMapping * size * scale; + mvCorner.x += offsetX; + mvCorner.y += offsetY; + // if(ortho){ + // mvCorner.xyz += normalize(-uCameraPosition) * offsetZ; + // } else { + // mvCorner.xyz += normalize(-mvCorner.xyz) * offsetZ; + // } + mvCorner.xyz += normalize(-mvCorner.xyz) * offsetZ; + + gl_Position = uProjection * mvCorner; + + vViewPosition = -mvCorner.xyz; +} \ No newline at end of file diff --git a/src/tests/browser/render-text.ts b/src/tests/browser/render-text.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f3b36f59163b6c3ac7686f877b8b59a608d68cb --- /dev/null +++ b/src/tests/browser/render-text.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import './index.html' +import { Canvas3D } from 'mol-canvas3d/canvas3d'; +import { Geometry } from 'mol-geo/geometry/geometry'; +import { TextBuilder } from 'mol-geo/geometry/text/text-builder'; +import { Text } from 'mol-geo/geometry/text/text'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Color } from 'mol-util/color'; +import { createTextRenderObject, createSpheresRenderObject } from 'mol-gl/render-object'; +import { Representation } from 'mol-repr/representation'; +import { SpheresBuilder } from 'mol-geo/geometry/spheres/spheres-builder'; +import { Spheres } from 'mol-geo/geometry/spheres/spheres'; + +const parent = document.getElementById('app')! +parent.style.width = '100%' +parent.style.height = '100%' + +const canvas = document.createElement('canvas') +canvas.style.width = '100%' +canvas.style.height = '100%' +parent.appendChild(canvas) + +const canvas3d = Canvas3D.create(canvas, parent) +canvas3d.animate() + +function textRepr() { + const props: PD.Values<Text.Params> = { + ...PD.getDefaultValues(Text.Params), + attachment: 'middle-center', + fontSize: 96, + fontWeight: 'bold', + } + + const textBuilder = TextBuilder.create(props, 1, 1) + textBuilder.add('Hello world', 0, 0, 0, 0) + textBuilder.add('Добрый день', 0, 1, 0, 0) + textBuilder.add('美好的一天', 0, 2, 0, 0) + textBuilder.add('¿Cómo estás?', 0, -1, 0, 0) + textBuilder.add('αβγ Å', 0, -2, 0, 0) + const text = textBuilder.getText() + + const values = Text.createValuesSimple(text, props, Color(0xFFDD00), 1) + const state = Text.createRenderableState(props) + const renderObject = createTextRenderObject(values, state) + console.log('text', renderObject) + const repr = Representation.fromRenderObject('text', renderObject) + return repr +} + +function spheresRepr() { + const spheresBuilder = SpheresBuilder.create(2, 1) + spheresBuilder.add(5, 0, 0, 0) + spheresBuilder.add(-4, 1, 0, 0) + const spheres = spheresBuilder.getSpheres() + + const values = Spheres.createValuesSimple(spheres, {}, Color(0xFF0000), 1) + const state = Geometry.createRenderableState() + const renderObject = createSpheresRenderObject(values, state) + console.log('spheres', renderObject) + const repr = Representation.fromRenderObject('spheres', renderObject) + return repr +} + +canvas3d.add(textRepr()) +canvas3d.add(spheresRepr()) +canvas3d.resetCamera() \ No newline at end of file