@tolokoban/tgd
Version:
ToloGameDev library for WebGL2
229 lines • 17.5 kB
JavaScript
import { TgdMaterialFaceOrientation } from "./../../material/index.js";
import { TgdTransfo } from "./../../math/index.js";
import { TgdPainter } from "./../painter.js";
import { TgdProgram } from "./../../program/index.js";
import { TgdShaderFragment, TgdShaderVertex } from "./../../shader/index.js";
import { TgdTexture2D } from "./../../texture/index.js";
import { tgdCanvasCreatePalette } from "./../../utils/index.js";
import { TgdVertexArray } from "./../../vao/index.js";
import { makeCapsule } from "./capsule.js";
/**
* @example
* ```
* const factory = new TgdPainterSegmentsData()
* factory.add(
* [0, 0, 0, .2],
* [1, 0, 0, .1],
* )
* factory.add(
* [0, 0, 0, .2],
* [0, 1, 0, .1],
* )
* factory.add(
* [0, 0, 0, .2],
* [0, 0, 1, .1],
* )
* const segments = new TgdPainterSegments(
* segment, factory
* )
* ```
*/
export class TgdPainterSegmentsMorphing extends TgdPainter {
// private readonly painter: TgdPainter
constructor(context, options) {
super();
this.context = context;
this.transfo = new TgdTransfo();
this.minRadius = 1;
this.radiusMultiplier = 1;
this.radiusConstant = 1;
this.radiusSwitch = 0;
this.instanceCount = 0;
/**
* Mix between two datasets of a pair.
* 0.0 for the first one, 1.0 for the second one.
*/
this.mix = 0;
this.name = `TgdPainterSegments#${this.id}`;
this.radiusMultiplier = options.radiusMultiplier ?? 1;
const { roundness = 3, minRadius = 1, datasetsPairs } = options;
if (datasetsPairs.length === 0) {
throw new Error('[TgdPainterSegmentsMorphing] "datasetsPairs" must contain at least one pair of datasets!');
}
const geometry = makeCapsule(roundness);
const material = options.material ?? new TgdMaterialFaceOrientation();
this.material = material;
material.attPosition = geometry.attPosition;
material.attNormal = "normal";
material.attUV = "((mix(attUV0_A, attUV0_B, uniMix) + mix(attUV1_A, attUV1_B, uniMix)) * .5)";
this.minRadius = minRadius;
if (roundness > 127) {
throw new Error("[TgdPainterSegments] Max roundness is 127!");
}
if (roundness < 0) {
throw new Error("[TgdPainterSegments] Min roundness is 0!");
}
this.colorTexture = new TgdTexture2D(context)
.setParams({
magFilter: "NEAREST",
minFilter: "NEAREST",
wrapR: "CLAMP_TO_EDGE",
wrapS: "CLAMP_TO_EDGE",
wrapT: "CLAMP_TO_EDGE",
})
.loadBitmap(tgdCanvasCreatePalette(["#f44", "#ff4", "#4f4", "#4ff", "#44f"]));
const vert = new TgdShaderVertex({
uniforms: {
uniMix: "float",
uniTransfoMatrix: "mat4",
uniModelViewMatrix: "mat4",
uniProjectionMatrix: "mat4",
uniPixelPerScreenUnit: "float",
uniMinRadiusInPixel: "float",
uniRadiusMultiplier: "float",
...material.uniforms,
},
attributes: {
[geometry.attPosition]: "vec4",
[geometry.attNormal]: "vec3",
attTip: "float",
attXYZR0_A: "vec4",
attXYZR0_B: "vec4",
attXYZR1_A: "vec4",
attXYZR1_B: "vec4",
attUV0_A: "vec2",
attUV0_B: "vec2",
attUV1_A: "vec2",
attUV1_B: "vec2",
},
varying: {
...material.varyings,
},
functions: {
...material.extraVertexShaderFunctions,
getPosition: [
"vec4 getPosition(vec4 pos) {",
[material.vertexShaderCodeForGetPosition ?? "return pos;"],
"}",
],
applyMaterial: [
"void applyMaterial(vec3 position, vec3 normal, vec2 uv) {",
[material.vertexShaderCode],
"}",
],
},
mainCode: [
"vec4 attXYZR0 = mix(attXYZR0_A, attXYZR0_B, uniMix);",
"vec4 attXYZR1 = mix(attXYZR1_A, attXYZR1_B, uniMix);",
"vec2 attUV0 = mix(attUV0_A, attUV0_B, uniMix);",
"vec2 attUV1 = mix(attUV1_A, attUV1_B, uniMix);",
`vec3 normal = ${geometry.attNormal};`,
`vec3 pos = getPosition(${geometry.attPosition}).xyz;`,
"vec4 xyzr = mix(attXYZR0, attXYZR1, attTip);",
"vec3 center = xyzr.xyz;",
"vec4 centerInCameraSpace = uniModelViewMatrix * uniTransfoMatrix * vec4(center, 1);",
"vec4 centerInScreenSpace = uniProjectionMatrix * centerInCameraSpace;",
"float screenUnitPerCameraUnit = (uniProjectionMatrix * vec4(0, 1, centerInCameraSpace.z, 1)).y / centerInScreenSpace.w;",
"float minRadiusInCameraUnit = uniMinRadiusInPixel / (uniPixelPerScreenUnit * screenUnitPerCameraUnit);",
"float radius = max(",
["xyzr.w * uniRadiusMultiplier,", "minRadiusInCameraUnit"],
");",
"vec3 dir = attXYZR1.xyz - attXYZR0.xyz;",
"float len = length(dir);",
"if (len == 0.0) {",
["// Just a sphere", "pos *= radius;", "pos += center.xyz;"],
"} else {",
[
"// Full capsule",
"vec3 Z = dir / len;",
"bool yUp = abs(Z.y) < 0.9;",
"vec3 fakeY = yUp ? vec3(0,1,0) : vec3(0,0,1);",
"vec3 X = cross(fakeY, Z);",
"vec3 Y = cross(Z, X);",
"mat3 mat = mat3(X, Y, Z);",
"pos *= radius;",
"pos = mat * pos + center.xyz;",
"normal = mat * normal;",
],
"}",
"gl_Position = uniProjectionMatrix * uniModelViewMatrix * uniTransfoMatrix * vec4(pos, 1);",
`applyMaterial(pos, normal, ${material.attUV}.xy);`,
],
}).code;
const frag = new TgdShaderFragment({
header: material.fragmentShaderHeader,
uniforms: material.uniforms,
outputs: { FragColor: "vec4" },
varying: material.varyings,
functions: {
...material.extraFragmentShaderFunctions,
applyMaterial: ["vec4 applyMaterial() {", [material.fragmentShaderCode], "}"],
},
mainCode: ["FragColor = applyMaterial();"],
}).code;
const prg = new TgdProgram(context.gl, {
name: `TgdPainterSegments/TgdProgram#${this.id}`,
vert,
frag,
});
this.prg = prg;
this.vaos = datasetsPairs.map(([A, B]) => {
const datasetA = extract(A).renameAttributes([
"attXYZR0_A",
"attUV0_A",
"attInfluence0_A",
"attXYZR1_A",
"attUV1_A",
"attInfluence1_A",
]);
const datasetB = extract(B).renameAttributes([
"attXYZR0_B",
"attUV0_B",
"attInfluence0_B",
"attXYZR1_B",
"attUV1_B",
"attInfluence1_B",
]);
if (datasetA.count !== datasetB.count) {
throw new Error(`[TgdPainterSegmentsMorphing] Datasets of a pair must have the same count, but we got ${datasetA.count} ≠ ${datasetB.count}!`);
}
const vao = new TgdVertexArray(context.gl, prg, [geometry.dataset, datasetA, datasetB], geometry.elements);
this.instanceCount = datasetA.count;
return vao;
});
this.vertexCount = geometry.elements?.length ?? 0;
}
delete() {
for (const vao of this.vaos)
vao.delete();
this.prg.delete();
}
paint(time, delta) {
const { context, prg, vao, vertexCount, instanceCount, material } = this;
const { gl, camera } = context;
gl.disable(gl.DITHER);
prg.use();
this.material.setUniforms?.({ program: prg, context, time, delta });
prg.uniform1f("uniMix", this.mix);
prg.uniform1f("uniPixelPerScreenUnit", gl.drawingBufferHeight);
prg.uniform1f("uniMinRadiusInPixel", this.minRadius);
prg.uniform1f("uniRadiusMultiplier", this.radiusMultiplier);
prg.uniformMatrix4fv("uniTransfoMatrix", this.transfo.matrix);
prg.uniformMatrix4fv("uniModelViewMatrix", camera.matrixModelView);
prg.uniformMatrix4fv("uniProjectionMatrix", camera.matrixProjection);
material.applyState(this.context, () => {
vao.bind();
gl.drawElementsInstanced(gl.TRIANGLES, vertexCount, gl.UNSIGNED_SHORT, 0, instanceCount);
vao.unbind();
});
}
get vao() {
return this.vaos[0];
}
}
function extract(arg) {
if (typeof arg === "function")
return arg();
return arg;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VnbWVudHMtbW9ycGhpbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcGFpbnRlci9zZWdtZW50cy9zZWdtZW50cy1tb3JwaGluZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQW9CLDBCQUEwQixFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQzVFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFDdEMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFBO0FBQ2pELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxjQUFjLENBQUE7QUFDekMsT0FBTyxFQUFFLGlCQUFpQixFQUFFLGVBQWUsRUFBRSxNQUFNLGFBQWEsQ0FBQTtBQUNoRSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBQzNDLE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUNuRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBQ3pDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFpQ3ZDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9CRztBQUNILE1BQU0sT0FBTywwQkFBMkIsU0FBUSxVQUFVO0lBbUJ0RCx1Q0FBdUM7SUFFdkMsWUFDdUIsT0FBbUIsRUFDdEMsT0FBMEM7UUFFMUMsS0FBSyxFQUFFLENBQUE7UUFIWSxZQUFPLEdBQVAsT0FBTyxDQUFZO1FBckIxQixZQUFPLEdBQUcsSUFBSSxVQUFVLEVBQUUsQ0FBQTtRQUVuQyxjQUFTLEdBQUcsQ0FBQyxDQUFBO1FBQ2IscUJBQWdCLEdBQUcsQ0FBQyxDQUFBO1FBQ3BCLG1CQUFjLEdBQUcsQ0FBQyxDQUFBO1FBQ2xCLGlCQUFZLEdBQUcsQ0FBQyxDQUFBO1FBQ2hCLGtCQUFhLEdBQUcsQ0FBQyxDQUFBO1FBQ3hCOzs7V0FHRztRQUNJLFFBQUcsR0FBRyxDQUFDLENBQUE7UUFj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