UNPKG

@tolokoban/tgd

Version:

ToloGameDev library for WebGL2

171 lines 16.6 kB
import { tgdColorMakeHueWheel } from "./../../color/index.js"; import { TgdDataset } from "./../../dataset/index.js"; import { TgdProgram } from "./../../program/index.js"; import { TgdShaderFragment, TgdShaderVertex } from "./../../shader/index.js"; import { TgdTexture2D } from "./../../texture/index.js"; import { tgdCanvasCreateGradientHorizontal } from "./../../utils/index.js"; import { TgdVertexArray } from "./../../vao/index.js"; import { TgdPainter } from "../painter.js"; export class TgdPainterPointsCloud extends TgdPainter { /** * Draw spheres with simple diffuse/specular material. */ static fragCodeSphere(options = {}) { const { specularExponent, specularIntensity, shadowThickness, shadowIntensity, light } = options; const SpE = typeof specularExponent === "number" ? specularExponent.toFixed(6) : "uniSpecularExponent"; const SpI = typeof specularIntensity === "number" ? specularIntensity.toFixed(6) : "uniSpecularIntensity"; const ShT = typeof shadowThickness === "number" ? shadowThickness.toFixed(6) : "uniShadowThickness"; const ShI = typeof shadowIntensity === "number" ? shadowIntensity.toFixed(6) : "uniShadowIntensity"; const L = typeof light === "number" ? light.toFixed(6) : "uniLight"; return [ "vec2 coords = 2.0 * (gl_PointCoord - vec2(.5));", "float len = 1.0 - dot(coords, coords);", "if (len < 0.0) discard;", "gl_FragDepth = gl_FragCoord.z - len * 1e-5;", `float light = smoothstep(0.0, ${ShT}, len) * ${ShI} + (1.0 - ${ShI});`, specularIntensity ? `float spec = pow(len, ${SpE}) * ${SpI};` : "// No specular.", `return color * vec4(vec3(light * ${L}), 1.0) + vec4(vec3(spec), 0.0);`, ]; } constructor(context, options) { super(); this.context = context; this.radiusMultiplier = 1; this.minSizeInPixels = 0; this.specularExponent = 10; this.specularIntensity = 0.33; this.shadowIntensity = 0.5; this.shadowThickness = 1; this.light = 1; this.name = options.name ?? this.name; this.radiusMultiplier = options.radiusMultiplier ?? 1; this.minSizeInPixels = options.minSizeInPixels ?? 0; this.specularExponent = options.specularExponent ?? 10; this.specularIntensity = options.specularIntensity ?? 0.33; this.shadowIntensity = options.shadowIntensity ?? 0.5; this.shadowThickness = options.shadowThickness ?? 1; this.light = options.light ?? 1; this.dataPoint = options.dataPoint; if ((this.dataPoint.length & 3) !== 0) { throw new Error(`dataPoint must have a length that is an integral multiple of 4: [x, y, z, radius, ...]!\ndataPoint.length === ${this.dataPoint.length}`); } this.dataUV = options.dataUV ?? new Float32Array(this.dataPoint.length >> 1); if (this.dataPoint.length !== this.dataUV.length * 2) { const message = `dataUV must be half of the size of dataPoint: [u, v, ...]!\ndataPoint.length === ${this.dataPoint.length}, \ndataUV.length === ${this.dataUV.length}`; context.console.error("[TgdPainterPointsCloud]", message); context.console.error("[TgdPainterPointsCloud] options =", options); context.console.error("[TgdPainterPointsCloud] this.dataPoint =", this.dataPoint); context.console.error("[TgdPainterPointsCloud] this.dataUV =", this.dataUV); throw new Error(message); } if (options.texture) { this.texture = options.texture; this.textureMustBeDeleted = false; } else { this.texture = new TgdTexture2D(context).loadBitmap(tgdCanvasCreateGradientHorizontal(128, tgdColorMakeHueWheel({ luminance: 0.2 }))); this.textureMustBeDeleted = true; } this.count = this.dataUV.length >> 1; this.dataset = this.createDataset(); this.program = this.createProgram(options.fragCode); this.vao = new TgdVertexArray(context.gl, this.program, [this.dataset]); } delete() { if (this.textureMustBeDeleted) this.texture.delete(); this.program.delete(); this.vao.delete(); } paint() { const { context, program, vao, texture, count, radiusMultiplier, minSizeInPixels } = this; const { gl, camera } = context; program.use(); texture.activate(0, program, "uniTexture"); program.uniform1f("uniRadiusMultiplier", radiusMultiplier); program.uniform1f("uniMinSizeInPixels", minSizeInPixels); program.uniform1f("uniScreenHeightInPixels", context.height); program.uniform1f("uniSpecularExponent", this.specularExponent); program.uniform1f("uniSpecularIntensity", this.specularIntensity); program.uniform1f("uniShadowIntensity", this.shadowIntensity); program.uniform1f("uniShadowThickness", this.shadowThickness); program.uniform1f("uniLight", this.light); program.uniformMatrix4fv("uniModelViewMatrix", camera.matrixModelView); program.uniformMatrix4fv("uniProjectionMatrix", camera.matrixProjection); vao.bind(); gl.drawArrays(gl.POINTS, 0, count); vao.unbind(); } createDataset() { const dataset = new TgdDataset({ attPoint: "vec4", attUV: "vec2", }); dataset.set("attPoint", this.dataPoint); dataset.set("attUV", this.dataUV); return dataset; } createProgram(render) { const vert = new TgdShaderVertex({ uniforms: { uniMinSizeInPixels: "float", uniRadiusMultiplier: "float", uniScreenHeightInPixels: "float", uniModelViewMatrix: "mat4", uniProjectionMatrix: "mat4", }, attributes: { attPoint: "vec4", attUV: "vec2", }, varying: { varUV: "vec2", }, mainCode: [ "varUV = attUV;", "float radius = attPoint.w;", "vec4 point = uniModelViewMatrix * vec4(attPoint.xyz, 1.0);", "vec4 shift = point + vec4(0, uniRadiusMultiplier * radius, 0, 0);", "gl_Position = uniProjectionMatrix * point;", "vec4 screenShift = uniProjectionMatrix * shift;", "gl_PointSize = max(", " uniMinSizeInPixels,", " abs(screenShift.y - gl_Position.y) * uniScreenHeightInPixels / gl_Position.w", ");", ], }).code; const frag = new TgdShaderFragment({ uniforms: { uniTexture: "sampler2D", uniSpecularExponent: "float", uniSpecularIntensity: "float", uniShadowIntensity: "float", uniShadowThickness: "float", uniLight: "float", }, varying: { varUV: "vec2", }, outputs: { FragColor: "vec4" }, functions: { render: [ "vec4 render(vec4 color) {", render ?? [ "vec2 coords = 2.0 * (gl_PointCoord - vec2(.5));", "float len = 1.0 - dot(coords, coords);", "if (len < 0.0) discard;", "gl_FragDepth = gl_FragCoord.z - len * 1e-5;", `float light = smoothstep(0.0, uniShadowThickness, len) * uniShadowIntensity + (1.0 - uniShadowIntensity);`, `float spec = pow(len, uniSpecularExponent) * uniSpecularIntensity;`, "return color * vec4(vec3(light * uniLight), 1.0) + vec4(vec3(spec), 0.0);", ], "}", ], }, mainCode: ["vec4 color = texture(uniTexture, varUV);", "FragColor = render(color);"], }).code; const program = new TgdProgram(this.context.gl, { vert, frag }); return program; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9pbnRzLWNsb3VkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3BhaW50ZXIvcG9pbnRzLWNsb3VkL3BvaW50cy1jbG91ZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFFakQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBQ3pDLE9BQU8sRUFBZSxpQkFBaUIsRUFBRSxlQUFlLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDN0UsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUMzQyxPQUFPLEVBQUUsaUNBQWlDLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFDOUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLFVBQVUsQ0FBQTtBQUN6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sWUFBWSxDQUFBO0FBcUV2QyxNQUFNLE9BQU8scUJBQXNCLFNBQVEsVUFBVTtJQUNqRDs7T0FFRztJQUNILE1BQU0sQ0FBQyxjQUFjLENBQ2pCLFVBTUssRUFBRTtRQUVQLE1BQU0sRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxlQUFlLEVBQUUsZUFBZSxFQUFFLEtBQUssRUFBRSxHQUFHLE9BQU8sQ0FBQTtRQUNoRyxNQUFNLEdBQUcsR0FBRyxPQUFPLGdCQUFnQixLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxxQkFBcUIsQ0FBQTtRQUN0RyxNQUFNLEdBQUcsR0FBRyxPQUFPLGlCQUFpQixLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxzQkFBc0IsQ0FBQTtRQUN6RyxNQUFNLEdBQUcs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