@ikijs/engine 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,8 +86,10 @@ delta, so a backgrounded tab or a long hitch cannot snap the rig.
86
86
 
87
87
  ## Rendering notes
88
88
 
89
- - The whole pipeline is **premultiplied alpha** — the canvas is created with
90
- `premultipliedAlpha: true` and the shader premultiplies before blending.
89
+ - The whole pipeline is **premultiplied alpha** — textures are uploaded
90
+ premultiplied (so LINEAR filtering never blends a transparent texel's rgb
91
+ into an edge), the shader keeps them so, and the canvas is created with
92
+ `premultipliedAlpha: true`.
91
93
  - Clipping masks use the stencil buffer. If the context grants no stencil, the
92
94
  affected parts render unclipped and `load()` logs it.
93
95
  - Textures are decoded from `data:` URIs only; external URLs are skipped with a
package/dist/index.js CHANGED
@@ -399,10 +399,10 @@ var IkiPlayer = class {
399
399
  this.canvas = canvas;
400
400
  const gl = canvas.getContext("webgl2", {
401
401
  alpha: true,
402
- // The whole pipeline is premultiplied-alpha: the fragment shader
403
- // multiplies rgb by alpha, the blend function is ONE /
404
- // ONE_MINUS_SRC_ALPHA, and the page compositor reads the framebuffer
405
- // as premultiplied. Straight-alpha (`premultipliedAlpha: false`) cannot
402
+ // The whole pipeline is premultiplied-alpha: textures are uploaded
403
+ // premultiplied, the fragment shader keeps them so, the blend function
404
+ // is ONE / ONE_MINUS_SRC_ALPHA, and the page compositor reads the
405
+ // framebuffer as premultiplied. Straight-alpha (`premultipliedAlpha: false`) cannot
406
406
  // be made consistent with SRC_ALPHA-style blending: semi-transparent
407
407
  // pixels over a transparent background come out premultiplied anyway
408
408
  // and composite too dark.
@@ -538,7 +538,7 @@ var IkiPlayer = class {
538
538
  return null;
539
539
  }
540
540
  gl.bindTexture(gl.TEXTURE_2D, texture);
541
- gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
541
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
542
542
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
543
543
  drainGlErrors(gl);
544
544
  gl.texImage2D(
@@ -949,14 +949,17 @@ uniform float u_alphaCutoff;
949
949
  in vec2 v_uv;
950
950
  out vec4 outColor;
951
951
  void main() {
952
+ // Texels are premultiplied (see the upload); the untextured fill is opaque
953
+ // white, premultiplied or not.
952
954
  vec4 base = u_useTexture ? texture(u_tex, v_uv) : vec4(1.0);
953
955
  vec4 tinted = base * u_color;
954
956
  // 0 for normal draws (no-op); raised during the stencil mask-write pass so
955
957
  // only opaque mask coverage marks the stencil (the transparent fringe is cut).
956
958
  if (tinted.a < u_alphaCutoff) discard;
957
- // Premultiply: the blend function and the canvas compositing contract
958
- // (premultipliedAlpha: true) both expect rgb already scaled by alpha.
959
- outColor = vec4(tinted.rgb * tinted.a, tinted.a);
959
+ // The blend function and the canvas compositing contract (premultipliedAlpha:
960
+ // true) both expect rgb already scaled by alpha. base.rgb is; the tint's own
961
+ // alpha still has to scale it (base.a is already folded into base.rgb).
962
+ outColor = vec4(tinted.rgb * u_color.a, tinted.a);
960
963
  }`;
961
964
  async function decodeTexture(source) {
962
965
  if (!source.startsWith("data:")) {
@@ -969,7 +972,7 @@ async function decodeTexture(source) {
969
972
  const blob = await (await fetch(source)).blob();
970
973
  return createImageBitmap(blob, {
971
974
  imageOrientation: "none",
972
- premultiplyAlpha: "none"
975
+ premultiplyAlpha: "premultiply"
973
976
  });
974
977
  }
975
978
  function createUnitQuad(gl) {
@@ -1073,7 +1076,10 @@ var FixedStepClock = class {
1073
1076
  // src/idle-motion.ts
1074
1077
  var BLINK_INTERVAL_MIN_MS = 1500;
1075
1078
  var BLINK_INTERVAL_MAX_MS = 6e3;
1076
- var BLINK_DURATION_MS = 120;
1079
+ var BLINK_CLOSE_MS = 60;
1080
+ var BLINK_HOLD_MS = 20;
1081
+ var BLINK_OPEN_MS = 100;
1082
+ var BLINK_DURATION_MS = BLINK_CLOSE_MS + BLINK_HOLD_MS + BLINK_OPEN_MS;
1077
1083
  var BREATH_PERIOD_MS = 3500;
1078
1084
  var GAZE_RADIUS = 0.3;
1079
1085
  var GAZE_RETARGET_MIN_MS = 1200;
@@ -1091,7 +1097,18 @@ function lerp(a, b, t) {
1091
1097
  return a + (b - a) * t;
1092
1098
  }
1093
1099
  function blinkEnvelope(phase) {
1094
- return 2 * Math.abs(phase - 0.5);
1100
+ const ms = phase * BLINK_DURATION_MS;
1101
+ if (ms <= 0) return 1;
1102
+ if (ms < BLINK_CLOSE_MS) {
1103
+ const t = ms / BLINK_CLOSE_MS;
1104
+ return 1 - t * t * (3 - 2 * t);
1105
+ }
1106
+ if (ms <= BLINK_CLOSE_MS + BLINK_HOLD_MS) return 0;
1107
+ if (ms < BLINK_DURATION_MS) {
1108
+ const t = (ms - BLINK_CLOSE_MS - BLINK_HOLD_MS) / BLINK_OPEN_MS;
1109
+ return 1 - (1 - t) * (1 - t);
1110
+ }
1111
+ return 1;
1095
1112
  }
1096
1113
  function randomInDisk(rng, radius) {
1097
1114
  const r = Math.sqrt(rng()) * radius;