UNPKG

@tolokoban/tgd

Version:

ToloGameDev library for WebGL2

198 lines 18.4 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 TgdPainterPointsCloudMorphing 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; /** * Mixing between two clouds: 0.0 (firts) and 1.0 (second). */ this.mix = 0; this.radiusMultiplier = 1; this.minSizeInPixels = 0; this.specularExponent = 10; this.specularIntensity = 0.33; this.shadowIntensity = 0.5; this.shadowThickness = 1; this.light = 1; this.datasets = []; this.name = options.name ?? this.name; this.mix = options.mix ?? 0; 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; 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; } if (options.data.length === 0) { throw new Error("[TgdPainterPointsCloud] options.data must not be empty!"); } this.counts = options.data.map(([{ point }]) => point.length >> 2); this.program = this.createProgram(options.fragCode); this.vaos = options.data.map(([dataA, dataB]) => { const datasetA = this.createDataset(dataA, "A"); const datasetB = this.createDataset(dataB, "B"); this.datasets.push(datasetA, datasetB); return new TgdVertexArray(context.gl, this.program, [datasetA, datasetB]); }); } 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("uniMix", this.mix); 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(data, suffix) { const { point } = data; if (point.length % 4 !== 0) { throw new Error(`[TgdPainterPointsCloud] point.length must be a multiple of 4! Current value: ${point.length}`); } const uv = data.uv ?? new Float32Array(point.length / 2); if (uv.length % 2 !== 0) { throw new Error(`[TgdPainterPointsCloud] uv.length must be a multiple of 2! Current value: ${uv.length}`); } if (point.length !== uv.length * 2) { throw new Error(`[TgdPainterPointsCloud] point.length must be twice the size of uv.length! point.length === ${point.length}, uv.length === ${uv.length}`); } const attPoint = `attPoint_${suffix}`; const attUV = `attUV_${suffix}`; const dataset = new TgdDataset({ [attPoint]: "vec4", [attUV]: "vec2", }); dataset.set(attPoint, point); dataset.set(attUV, uv); return dataset; } get count() { const [count] = this.counts; return count; } get vao() { const [vao] = this.vaos; return vao; } createProgram(render) { const vert = new TgdShaderVertex({ uniforms: { uniMix: "float", uniMinSizeInPixels: "float", uniRadiusMultiplier: "float", uniScreenHeightInPixels: "float", uniModelViewMatrix: "mat4", uniProjectionMatrix: "mat4", }, attributes: { attPoint_A: "vec4", attUV_A: "vec2", attPoint_B: "vec4", attUV_B: "vec2", }, varying: { varUV: "vec2", }, mainCode: [ "vec4 attPoint = mix(attPoint_A, attPoint_B, uniMix);", "vec2 attUV = mix(attUV_A, attUV_B, uniMix);", "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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9pbnRzLWNsb3VkLW1vcnBoaW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3BhaW50ZXIvcG9pbnRzLWNsb3VkL3BvaW50cy1jbG91ZC1tb3JwaGluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxZQUFZLENBQUE7QUFFakQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGNBQWMsQ0FBQTtBQUN6QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBQ3pDLE9BQU8sRUFBb0IsaUJBQWlCLEVBQUUsZUFBZSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQ2xGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxjQUFjLENBQUE7QUFDM0MsT0FBTyxFQUFFLGlDQUFpQyxFQUFFLE1BQU0sWUFBWSxDQUFBO0FBQzlELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxVQUFVLENBQUE7QUFDekMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQStFdkMsTUFBTSxPQUFPLDZCQUE4QixTQUFRLFVBQVU7SUFDekQ7O09BRUc7SUFDSCxNQUFNLENBQUMsY0FBYyxDQUNqQixVQU1LLEVBQUU7UUFFUCxNQUFNLEVBQUUsZ0JBQWdCLEVBQUUsaUJBQWlCLEVBQUUsZUFBZSxFQUFFLGVBQWUsRUFBRSxLQUFLLEVBQUUsR0FBRyxPQUFPLENBQUE7UUFDaEcsTUFBTSxHQUFHLEdBQUcsT0FBTyxnQkFBZ0IsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMscUJBQXFCLENBQUE7UUFDdEcsTUFBTSxHQUFHLEdBQUcsT0FBTyxpQkFBaUIsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsc0JBQXNCLENB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