@tolokoban/tgd
Version:
ToloGameDev library for WebGL2
198 lines • 18.4 kB
JavaScript
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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