cubeforge 0.4.5 → 0.4.7

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.
Files changed (2) hide show
  1. package/dist/index.js +140 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2225,6 +2225,88 @@ function createWhiteTexture(gl) {
2225
2225
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
2226
2226
  return tex;
2227
2227
  }
2228
+ var SHAPE_TEX_SIZE = 128;
2229
+ function drawShapeOnCanvas(ctx, shape, w, h, borderRadius, starPoints, starInnerRadius) {
2230
+ const cx = w / 2;
2231
+ const cy = h / 2;
2232
+ switch (shape) {
2233
+ case "circle": {
2234
+ const r = Math.min(w, h) / 2;
2235
+ ctx.beginPath();
2236
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
2237
+ break;
2238
+ }
2239
+ case "ellipse": {
2240
+ ctx.beginPath();
2241
+ ctx.ellipse(cx, cy, w / 2, h / 2, 0, 0, Math.PI * 2);
2242
+ break;
2243
+ }
2244
+ case "roundedRect": {
2245
+ const r = Math.min(borderRadius, w / 2, h / 2);
2246
+ ctx.beginPath();
2247
+ ctx.roundRect(0, 0, w, h, r);
2248
+ break;
2249
+ }
2250
+ case "triangle": {
2251
+ ctx.beginPath();
2252
+ ctx.moveTo(cx, 0);
2253
+ ctx.lineTo(w, h);
2254
+ ctx.lineTo(0, h);
2255
+ ctx.closePath();
2256
+ break;
2257
+ }
2258
+ case "pentagon": {
2259
+ drawRegularPolygonPath(ctx, cx, cy, Math.min(w, h) / 2, 5);
2260
+ break;
2261
+ }
2262
+ case "hexagon": {
2263
+ drawRegularPolygonPath(ctx, cx, cy, Math.min(w, h) / 2, 6);
2264
+ break;
2265
+ }
2266
+ case "star": {
2267
+ drawStarPath(ctx, cx, cy, Math.min(w, h) / 2, starPoints, starInnerRadius);
2268
+ break;
2269
+ }
2270
+ default: {
2271
+ ctx.beginPath();
2272
+ ctx.rect(0, 0, w, h);
2273
+ break;
2274
+ }
2275
+ }
2276
+ }
2277
+ function drawRegularPolygonPath(ctx, cx, cy, r, sides) {
2278
+ ctx.beginPath();
2279
+ for (let i = 0; i < sides; i++) {
2280
+ const angle = i * 2 * Math.PI / sides - Math.PI / 2;
2281
+ const px = cx + r * Math.cos(angle);
2282
+ const py = cy + r * Math.sin(angle);
2283
+ if (i === 0) ctx.moveTo(px, py);
2284
+ else ctx.lineTo(px, py);
2285
+ }
2286
+ ctx.closePath();
2287
+ }
2288
+ function drawStarPath(ctx, cx, cy, r, points, innerRatio) {
2289
+ const inner = r * innerRatio;
2290
+ ctx.beginPath();
2291
+ for (let i = 0; i < points * 2; i++) {
2292
+ const angle = i * Math.PI / points - Math.PI / 2;
2293
+ const radius = i % 2 === 0 ? r : inner;
2294
+ const px = cx + radius * Math.cos(angle);
2295
+ const py = cy + radius * Math.sin(angle);
2296
+ if (i === 0) ctx.moveTo(px, py);
2297
+ else ctx.lineTo(px, py);
2298
+ }
2299
+ ctx.closePath();
2300
+ }
2301
+ function getShapeKey(sprite) {
2302
+ const shape = sprite.shape ?? "rect";
2303
+ if (shape === "rect" && !sprite.borderRadius && !sprite.strokeColor) return "";
2304
+ const br = shape === "roundedRect" ? sprite.borderRadius ?? 0 : 0;
2305
+ const sp = shape === "star" ? sprite.starPoints ?? 5 : 0;
2306
+ const sir = shape === "star" ? sprite.starInnerRadius ?? 0.4 : 0;
2307
+ const stroke = sprite.strokeColor && (sprite.strokeWidth ?? 0) > 0 ? `|s:${sprite.strokeColor}:${sprite.strokeWidth}` : "";
2308
+ return `__shape__:${shape}:${br}:${sp}:${sir}${stroke}`;
2309
+ }
2228
2310
  function getSamplingKey(sampling) {
2229
2311
  if (!sampling) return "";
2230
2312
  if (typeof sampling === "string") return sampling;
@@ -2235,6 +2317,8 @@ function getTextureKey(sprite) {
2235
2317
  const samplingKey = getSamplingKey(sprite.sampling);
2236
2318
  const suffix = samplingKey ? `:s=${samplingKey}` : "";
2237
2319
  if (src) return sprite.tileX || sprite.tileY ? `${src}:repeat${suffix}` : `${src}${suffix}`;
2320
+ const shapeKey = getShapeKey(sprite);
2321
+ if (shapeKey) return shapeKey;
2238
2322
  return `__color__:${sprite.color}${suffix}`;
2239
2323
  }
2240
2324
  function getUVRect(sprite) {
@@ -2385,6 +2469,8 @@ var RenderSystem = class {
2385
2469
  textureCache = /* @__PURE__ */ new Map();
2386
2470
  /** Insertion-order key list for LRU-style eviction. */
2387
2471
  textureCacheKeys = [];
2472
+ // ── Shape texture cache ─────────────────────────────────────────────────
2473
+ shapeTextures = /* @__PURE__ */ new Map();
2388
2474
  // ── Render layer manager ────────────────────────────────────────────────
2389
2475
  layers = createRenderLayerManager();
2390
2476
  // ── Texture sampling ────────────────────────────────────────────────────
@@ -2542,6 +2628,43 @@ var RenderSystem = class {
2542
2628
  this.textureCacheKeys.push(key);
2543
2629
  return entry;
2544
2630
  }
2631
+ // ── Shape texture generation ──────────────────────────────────────────────
2632
+ getOrCreateShapeTexture(sprite) {
2633
+ const key = getShapeKey(sprite);
2634
+ const cached = this.shapeTextures.get(key);
2635
+ if (cached) return cached;
2636
+ const size = SHAPE_TEX_SIZE;
2637
+ const offscreen = document.createElement("canvas");
2638
+ offscreen.width = size;
2639
+ offscreen.height = size;
2640
+ const ctx2d = offscreen.getContext("2d");
2641
+ drawShapeOnCanvas(
2642
+ ctx2d,
2643
+ sprite.shape ?? "rect",
2644
+ size,
2645
+ size,
2646
+ (sprite.borderRadius ?? 0) / Math.max(sprite.width, sprite.height) * size,
2647
+ sprite.starPoints ?? 5,
2648
+ sprite.starInnerRadius ?? 0.4
2649
+ );
2650
+ ctx2d.fillStyle = "#ffffff";
2651
+ ctx2d.fill();
2652
+ if (sprite.strokeColor && (sprite.strokeWidth ?? 0) > 0) {
2653
+ ctx2d.strokeStyle = "#ffffff";
2654
+ ctx2d.lineWidth = (sprite.strokeWidth ?? 0) / Math.max(sprite.width, sprite.height) * size;
2655
+ ctx2d.stroke();
2656
+ }
2657
+ const gl = this.gl;
2658
+ const tex = gl.createTexture();
2659
+ gl.bindTexture(gl.TEXTURE_2D, tex);
2660
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, offscreen);
2661
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
2662
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
2663
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2664
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2665
+ this.shapeTextures.set(key, tex);
2666
+ return tex;
2667
+ }
2545
2668
  // ── Texture sampling helper ────────────────────────────────────────────────
2546
2669
  /** Apply min/mag filter params to the currently bound texture. */
2547
2670
  applySampling(spriteSampling) {
@@ -2570,12 +2693,20 @@ var RenderSystem = class {
2570
2693
  }
2571
2694
  }
2572
2695
  // ── Instanced draw call ────────────────────────────────────────────────────
2573
- flush(count, textureKey, sampling, blendMode) {
2696
+ flush(count, textureKey, sampling, blendMode, shapeSpriteRef) {
2574
2697
  if (count === 0) return;
2575
2698
  const { gl } = this;
2576
2699
  if (blendMode && blendMode !== "normal") this.applyBlendMode(blendMode);
2577
2700
  const isColor = textureKey.startsWith("__color__");
2578
- const tex = isColor ? this.whiteTexture : this.loadTexture(textureKey);
2701
+ const isShape = textureKey.startsWith("__shape__");
2702
+ let tex;
2703
+ if (isShape && shapeSpriteRef) {
2704
+ tex = this.getOrCreateShapeTexture(shapeSpriteRef);
2705
+ } else if (isColor) {
2706
+ tex = this.whiteTexture;
2707
+ } else {
2708
+ tex = this.loadTexture(textureKey);
2709
+ }
2579
2710
  gl.bindTexture(gl.TEXTURE_2D, tex);
2580
2711
  this.applySampling(sampling);
2581
2712
  gl.uniform1i(this.uUseTexture, isColor ? 0 : 1);
@@ -2816,9 +2947,10 @@ var RenderSystem = class {
2816
2947
  let batchKey = "";
2817
2948
  let batchSampling;
2818
2949
  let batchBlendMode = "normal";
2950
+ let batchShapeRef;
2819
2951
  for (let i = 0; i <= renderables.length; i++) {
2820
2952
  if (i === renderables.length) {
2821
- this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
2953
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode, batchShapeRef);
2822
2954
  break;
2823
2955
  }
2824
2956
  const id = renderables[i];
@@ -2869,12 +3001,14 @@ var RenderSystem = class {
2869
3001
  const key = getTextureKey(sprite);
2870
3002
  const spriteBlend = sprite.blendMode ?? "normal";
2871
3003
  if ((key !== batchKey || spriteBlend !== batchBlendMode) && batchCount > 0 || batchCount >= MAX_INSTANCES) {
2872
- this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
3004
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode, batchShapeRef);
2873
3005
  batchCount = 0;
2874
3006
  }
2875
3007
  batchKey = key;
2876
3008
  batchSampling = sprite.sampling;
2877
3009
  batchBlendMode = spriteBlend;
3010
+ if (key.startsWith("__shape__")) batchShapeRef = sprite;
3011
+ else batchShapeRef = void 0;
2878
3012
  const ss = world.getComponent(id, "SquashStretch");
2879
3013
  const scaleXMod = ss ? ss.currentScaleX : 1;
2880
3014
  const scaleYMod = ss ? ss.currentScaleY : 1;
@@ -2917,11 +3051,12 @@ var RenderSystem = class {
2917
3051
  if (!text.visible) continue;
2918
3052
  const entry = this.getOrCreateTextTexture(text);
2919
3053
  if (!entry) continue;
2920
- this.flush(batchCount, batchKey, batchSampling, batchBlendMode);
3054
+ this.flush(batchCount, batchKey, batchSampling, batchBlendMode, batchShapeRef);
2921
3055
  batchCount = 0;
2922
3056
  batchKey = "";
2923
3057
  batchSampling = void 0;
2924
3058
  batchBlendMode = "normal";
3059
+ batchShapeRef = void 0;
2925
3060
  this.writeInstance(
2926
3061
  0,
2927
3062
  transform.x + text.offsetX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubeforge",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "React-first 2D browser game engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",