nova64 0.2.4 → 0.2.6
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 +25 -8
- package/bin/nova64.js +165 -0
- package/dist/assets/console-CY_kygm3.js +14 -0
- package/dist/assets/console-CY_kygm3.js.map +1 -0
- package/dist/assets/main-l0sNRNKZ.js.map +1 -0
- package/dist/assets/sky/studio/nx.png +0 -0
- package/dist/assets/sky/studio/ny.png +0 -0
- package/dist/assets/sky/studio/nz.png +0 -0
- package/dist/assets/sky/studio/px.png +0 -0
- package/dist/assets/sky/studio/py.png +0 -0
- package/dist/assets/sky/studio/pz.png +0 -0
- package/dist/assets/vanilla-Dcuy32gi.js +2 -0
- package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
- package/dist/console.html +899 -0
- package/dist/docs/BENCHMARK.md +77 -0
- package/dist/docs/CHEATSHEET.md +255 -0
- package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
- package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
- package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
- package/dist/docs/FREE_GLB_ASSETS.md +330 -0
- package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
- package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
- package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
- package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
- package/dist/docs/MIGRATION_GUIDE.md +553 -0
- package/dist/docs/NOVA64_3D_API.md +356 -0
- package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
- package/dist/docs/NOVA64_UI_API.md +503 -0
- package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
- package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
- package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
- package/dist/docs/api-3d.html +750 -0
- package/dist/docs/api-effects.html +385 -0
- package/dist/docs/api-improvements.md +121 -0
- package/dist/docs/api-skybox.html +407 -0
- package/dist/docs/api-sprites.html +321 -0
- package/dist/docs/api-voxel.html +337 -0
- package/dist/docs/api.html +543 -0
- package/dist/docs/assets.html +306 -0
- package/dist/docs/audio.html +340 -0
- package/dist/docs/blogs.html +286 -0
- package/dist/docs/collision.html +316 -0
- package/dist/docs/console.html +247 -0
- package/dist/docs/editor.html +297 -0
- package/dist/docs/font.html +247 -0
- package/dist/docs/framebuffer.html +247 -0
- package/dist/docs/fullscreen-button.html +297 -0
- package/dist/docs/gpu-systems.html +247 -0
- package/dist/docs/index.html +580 -0
- package/dist/docs/input.html +491 -0
- package/dist/docs/physics.html +311 -0
- package/dist/docs/screens.html +311 -0
- package/dist/docs/storage.html +311 -0
- package/dist/docs/textinput.html +332 -0
- package/dist/docs/ui.html +488 -0
- package/dist/examples/3d-advanced/code.js +695 -0
- package/dist/examples/adventure-comic-3d/code.js +342 -0
- package/dist/examples/audio-lab/code.js +150 -0
- package/dist/examples/boids-flocking/code.js +270 -0
- package/dist/examples/crystal-cathedral-3d/code.js +706 -0
- package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
- package/dist/examples/demoscene/README.md +192 -0
- package/dist/examples/demoscene/code.js +1081 -0
- package/dist/examples/demoscene/meta.json +21 -0
- package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
- package/dist/examples/f-zero-nova-3d/code.js +865 -0
- package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
- package/dist/examples/fps-demo-3d/code.js +744 -0
- package/dist/examples/game-of-life-3d/code.js +338 -0
- package/dist/examples/generative-art/code.js +632 -0
- package/dist/examples/hello-3d/code.js +325 -0
- package/dist/examples/hello-skybox/code.js +183 -0
- package/dist/examples/hello-world/code.js +19 -0
- package/dist/examples/input-showcase/code.js +109 -0
- package/dist/examples/instancing-demo/code.js +315 -0
- package/dist/examples/minecraft-demo/code.js +387 -0
- package/dist/examples/model-viewer-3d/code.js +114 -0
- package/dist/examples/mystical-realm-3d/code.js +1203 -0
- package/dist/examples/nature-explorer-3d/code.js +1318 -0
- package/dist/examples/particles-demo/code.js +522 -0
- package/dist/examples/pbr-showcase/code.js +140 -0
- package/dist/examples/physics-demo-3d/code.js +948 -0
- package/dist/examples/screen-demo/code.js +267 -0
- package/dist/examples/shooter-demo-3d/code.js +1286 -0
- package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
- package/dist/examples/space-combat-3d/README.md +135 -0
- package/dist/examples/space-combat-3d/code.js +1332 -0
- package/dist/examples/space-harrier-3d/code.js +923 -0
- package/dist/examples/star-fox-nova-3d/code.js +1116 -0
- package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
- package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
- package/dist/examples/storage-quest/code.js +209 -0
- package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
- package/dist/examples/strider-demo-3d/cache-test.html +132 -0
- package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
- package/dist/examples/strider-demo-3d/code-old.js +1537 -0
- package/dist/examples/strider-demo-3d/code.js +1462 -0
- package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
- package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
- package/dist/examples/super-plumber-64/README.md +128 -0
- package/dist/examples/super-plumber-64/code.js +1185 -0
- package/dist/examples/super-plumber-64/index.html +88 -0
- package/dist/examples/test-2d-overlay/code.js +32 -0
- package/dist/examples/test-font/code.js +51 -0
- package/dist/examples/test-minimal/code.js +21 -0
- package/dist/examples/ui-demo/code.js +306 -0
- package/dist/examples/wing-commander-space/README.md +180 -0
- package/dist/examples/wing-commander-space/code.js +1285 -0
- package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
- package/dist/examples/wizardry-3d/code.js +3928 -0
- package/dist/index.html +666 -0
- package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/dist/os9-shell/index.html +23 -0
- package/dist/os9-shell/nova-icon.svg +12 -0
- package/index.html +6 -1
- package/package.json +37 -32
- package/public/assets/sky/studio/nx.png +0 -0
- package/public/assets/sky/studio/ny.png +0 -0
- package/public/assets/sky/studio/nz.png +0 -0
- package/public/assets/sky/studio/px.png +0 -0
- package/public/assets/sky/studio/py.png +0 -0
- package/public/assets/sky/studio/pz.png +0 -0
- package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
- package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
- package/public/os9-shell/index.html +10 -1
- package/runtime/api-2d.js +301 -21
- package/runtime/api-3d/pbr.js +45 -1
- package/runtime/api-3d.js +1 -0
- package/runtime/api-effects.js +90 -3
- package/runtime/api-gameutils.js +476 -0
- package/runtime/api-generative.js +610 -0
- package/runtime/api-skybox.js +54 -0
- package/runtime/api-voxel.js +139 -28
- package/runtime/gpu-threejs.js +13 -9
- package/runtime/ui.js +2 -2
- package/src/main.js +24 -1
- package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
- package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
+
<base href="/os9-shell/" />
|
|
5
6
|
<link rel="icon" type="image/svg+xml" href="./nova-icon.svg" />
|
|
6
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
8
|
<title>nova64 OS</title>
|
|
8
|
-
<script type="
|
|
9
|
+
<script type="importmap">
|
|
10
|
+
{
|
|
11
|
+
"imports": {
|
|
12
|
+
"three": "https://esm.sh/three@0.182.0",
|
|
13
|
+
"three/examples/jsm/": "https://esm.sh/three@0.182.0/examples/jsm/"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
17
|
+
<script type="module" crossorigin src="./assets/index-KchE_ngx.js"></script>
|
|
9
18
|
<link rel="stylesheet" crossorigin href="./assets/index-DIHfrTaW.css">
|
|
10
19
|
</head>
|
|
11
20
|
<body>
|
package/runtime/api-2d.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// printCentered, printRight, drawScanlines, drawNoise, drawProgressBar,
|
|
7
7
|
// drawGlowText, drawPixelBorder, drawCheckerboard, colorLerp, colorMix, hexColor,
|
|
8
8
|
// drawStarburst, drawDiamond, drawTriangle, drawWave, drawSpiral, scrollingText,
|
|
9
|
-
// drawPanel, drawHealthBar, drawMinimap, n64Palette
|
|
9
|
+
// drawPanel, drawHealthBar, createMinimap, drawMinimap, n64Palette
|
|
10
10
|
|
|
11
11
|
import { rgba8 } from './api.js';
|
|
12
12
|
|
|
@@ -731,29 +731,272 @@ export function api2d(gpu) {
|
|
|
731
731
|
}
|
|
732
732
|
}
|
|
733
733
|
|
|
734
|
+
// ── Minimap System ─────────────────────────────────────────────────────────
|
|
735
|
+
|
|
734
736
|
/**
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
737
|
+
* createMinimap(opts) — create a reusable minimap configuration.
|
|
738
|
+
*
|
|
739
|
+
* opts:
|
|
740
|
+
* x, y — screen position (default: bottom-right corner)
|
|
741
|
+
* width, height — pixel dimensions (default: 80×80)
|
|
742
|
+
* bgColor — background fill (default: semi-transparent black)
|
|
743
|
+
* borderLight, borderDark — border colours (null to disable border)
|
|
744
|
+
* shape — 'rect' | 'circle' (default: 'rect')
|
|
745
|
+
* follow — entity to center on (object with .x, .y in world coords)
|
|
746
|
+
* worldW, worldH — world-space bounds for coordinate mapping
|
|
747
|
+
* tileW, tileH — if set, map is tile-based (grid cells)
|
|
748
|
+
* tileScale — pixels per tile (default: 2)
|
|
749
|
+
* tiles — fn(tx,ty)→color|null or 2D array; null/0 = skip
|
|
750
|
+
* fogOfWar — if set, only reveal tiles within this radius of follow entity
|
|
751
|
+
* entities — array of { x, y, color, size?, label? }
|
|
752
|
+
* player — shorthand for the player entity { x, y, color?, blink? }
|
|
753
|
+
* sweep — { speed, color } for animated radar sweep line
|
|
754
|
+
* gridLines — number of grid divisions (0 = none)
|
|
755
|
+
* gridColor — colour for grid lines
|
|
756
|
+
*
|
|
757
|
+
* Returns a minimap object you pass to drawMinimap().
|
|
758
|
+
* You can mutate its properties between frames (e.g. update entities).
|
|
738
759
|
*/
|
|
739
|
-
function
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
760
|
+
function createMinimap(opts = {}) {
|
|
761
|
+
return {
|
|
762
|
+
x: opts.x ?? W - 90,
|
|
763
|
+
y: opts.y ?? 10,
|
|
764
|
+
width: opts.width ?? 80,
|
|
765
|
+
height: opts.height ?? 80,
|
|
766
|
+
bgColor: opts.bgColor ?? rgba8(0, 0, 0, 180),
|
|
767
|
+
borderLight: opts.borderLight !== undefined ? opts.borderLight : rgba8(150, 150, 150),
|
|
768
|
+
borderDark: opts.borderDark !== undefined ? opts.borderDark : rgba8(50, 50, 50),
|
|
769
|
+
shape: opts.shape ?? 'rect',
|
|
770
|
+
follow: opts.follow ?? null,
|
|
771
|
+
worldW: opts.worldW ?? 100,
|
|
772
|
+
worldH: opts.worldH ?? 100,
|
|
773
|
+
tileW: opts.tileW ?? 0,
|
|
774
|
+
tileH: opts.tileH ?? 0,
|
|
775
|
+
tileScale: opts.tileScale ?? 2,
|
|
776
|
+
tiles: opts.tiles ?? null,
|
|
777
|
+
fogOfWar: opts.fogOfWar ?? 0,
|
|
778
|
+
entities: opts.entities ?? [],
|
|
779
|
+
player: opts.player ?? null,
|
|
780
|
+
sweep: opts.sweep ?? null,
|
|
781
|
+
gridLines: opts.gridLines ?? 0,
|
|
782
|
+
gridColor: opts.gridColor ?? rgba8(40, 60, 40, 120),
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/** Internal: check if pixel is inside a circle */
|
|
787
|
+
function _inCircle(px, py, cx, cy, r) {
|
|
788
|
+
const dx = px - cx,
|
|
789
|
+
dy = py - cy;
|
|
790
|
+
return dx * dx + dy * dy <= r * r;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* drawMinimap(minimap, time?)
|
|
795
|
+
*
|
|
796
|
+
* Accepts EITHER a createMinimap() object, OR the legacy signature:
|
|
797
|
+
* drawMinimap(x, y, size, entities, bgColor)
|
|
798
|
+
*
|
|
799
|
+
* time is optional — only needed for sweep animation and player blinking.
|
|
800
|
+
*/
|
|
801
|
+
function drawMinimap(minimapOrX, timeOrY, sizeArg, entitiesArg, bgColorArg) {
|
|
802
|
+
// Legacy compat: drawMinimap(x, y, size, entities, bgColor)
|
|
803
|
+
let mm;
|
|
804
|
+
let time = 0;
|
|
805
|
+
if (typeof minimapOrX === 'number') {
|
|
806
|
+
mm = createMinimap({
|
|
807
|
+
x: minimapOrX,
|
|
808
|
+
y: timeOrY,
|
|
809
|
+
width: sizeArg,
|
|
810
|
+
height: sizeArg,
|
|
811
|
+
worldW: 100,
|
|
812
|
+
worldH: 100,
|
|
813
|
+
bgColor: bgColorArg,
|
|
814
|
+
});
|
|
815
|
+
// Convert legacy entities (they carry worldW/worldH per-entity)
|
|
816
|
+
if (Array.isArray(entitiesArg)) {
|
|
817
|
+
mm.entities = entitiesArg.map(e => ({
|
|
818
|
+
x: e.x,
|
|
819
|
+
y: e.y,
|
|
820
|
+
color: e.color ?? rgba8(255, 255, 255),
|
|
821
|
+
size: 2,
|
|
822
|
+
}));
|
|
823
|
+
// Use first entity's world bounds if provided
|
|
824
|
+
if (entitiesArg.length > 0) {
|
|
825
|
+
mm.worldW = entitiesArg[0].worldW ?? 100;
|
|
826
|
+
mm.worldH = entitiesArg[0].worldH ?? 100;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
} else {
|
|
830
|
+
mm = minimapOrX;
|
|
831
|
+
time = timeOrY ?? 0;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const { x, y, width: mw, height: mh, shape } = mm;
|
|
835
|
+
const cx = x + mw / 2,
|
|
836
|
+
cy = y + mh / 2;
|
|
837
|
+
const isCircle = shape === 'circle';
|
|
838
|
+
const radius = Math.min(mw, mh) / 2;
|
|
839
|
+
|
|
840
|
+
// 1. Background fill
|
|
841
|
+
const bgc = _unpack(mm.bgColor);
|
|
842
|
+
for (let py = y; py < y + mh; py++) {
|
|
843
|
+
for (let px = x; px < x + mw; px++) {
|
|
844
|
+
if (isCircle && !_inCircle(px, py, cx, cy, radius)) continue;
|
|
845
|
+
_blend(px, py, bgc.r, bgc.g, bgc.b, bgc.a);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// 2. Tile map rendering
|
|
850
|
+
if (mm.tiles && mm.tileW > 0 && mm.tileH > 0) {
|
|
851
|
+
const ts = mm.tileScale;
|
|
852
|
+
const isFunc = typeof mm.tiles === 'function';
|
|
853
|
+
const followTX = mm.follow ? Math.floor(mm.follow.x) : 0;
|
|
854
|
+
const followTY = mm.follow ? Math.floor(mm.follow.y) : 0;
|
|
855
|
+
|
|
856
|
+
for (let ty = 0; ty < mm.tileH; ty++) {
|
|
857
|
+
for (let tx = 0; tx < mm.tileW; tx++) {
|
|
858
|
+
// Fog of war check
|
|
859
|
+
if (mm.fogOfWar > 0 && mm.follow) {
|
|
860
|
+
const dist = Math.abs(tx - followTX) + Math.abs(ty - followTY);
|
|
861
|
+
if (dist > mm.fogOfWar) continue;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const tileColor = isFunc ? mm.tiles(tx, ty) : mm.tiles[ty] ? mm.tiles[ty][tx] : null;
|
|
865
|
+
if (!tileColor) continue;
|
|
866
|
+
|
|
867
|
+
const tc = _unpack(tileColor);
|
|
868
|
+
const px0 = x + tx * ts;
|
|
869
|
+
const py0 = y + ty * ts;
|
|
870
|
+
for (let dy = 0; dy < ts; dy++) {
|
|
871
|
+
for (let dx = 0; dx < ts; dx++) {
|
|
872
|
+
const px = px0 + dx,
|
|
873
|
+
py = py0 + dy;
|
|
874
|
+
if (isCircle && !_inCircle(px, py, cx, cy, radius)) continue;
|
|
875
|
+
_blend(px, py, tc.r, tc.g, tc.b, tc.a);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// 3. Grid lines
|
|
883
|
+
if (mm.gridLines > 0) {
|
|
884
|
+
const gc = _unpack(mm.gridColor);
|
|
885
|
+
for (let i = 1; i < mm.gridLines; i++) {
|
|
886
|
+
// Vertical line
|
|
887
|
+
const gx = (x + (mw / mm.gridLines) * i) | 0;
|
|
888
|
+
for (let py2 = y; py2 < y + mh; py2++) {
|
|
889
|
+
if (isCircle && !_inCircle(gx, py2, cx, cy, radius)) continue;
|
|
890
|
+
_blend(gx, py2, gc.r, gc.g, gc.b, gc.a);
|
|
891
|
+
}
|
|
892
|
+
// Horizontal line
|
|
893
|
+
const gy = (y + (mh / mm.gridLines) * i) | 0;
|
|
894
|
+
for (let px2 = x; px2 < x + mw; px2++) {
|
|
895
|
+
if (isCircle && !_inCircle(px2, gy, cx, cy, radius)) continue;
|
|
896
|
+
_blend(px2, gy, gc.r, gc.g, gc.b, gc.a);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// 4. Radar sweep line
|
|
902
|
+
if (mm.sweep) {
|
|
903
|
+
const angle = time * (mm.sweep.speed ?? 2);
|
|
904
|
+
const sc = _unpack(mm.sweep.color ?? rgba8(0, 255, 0, 100));
|
|
905
|
+
const sx = Math.cos(angle) * radius;
|
|
906
|
+
const sy = Math.sin(angle) * radius;
|
|
907
|
+
// Bresenham-ish sweep from center
|
|
908
|
+
const steps = Math.max(Math.abs(sx), Math.abs(sy)) | 0;
|
|
909
|
+
if (steps > 0) {
|
|
910
|
+
for (let s = 0; s <= steps; s++) {
|
|
911
|
+
const t = s / steps;
|
|
912
|
+
const px = (cx + sx * t) | 0;
|
|
913
|
+
const py = (cy + sy * t) | 0;
|
|
914
|
+
if (px < x || px >= x + mw || py < y || py >= y + mh) continue;
|
|
915
|
+
if (isCircle && !_inCircle(px, py, cx, cy, radius)) continue;
|
|
916
|
+
_blend(px, py, sc.r, sc.g, sc.b, sc.a);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Helper: convert world coords to screen pixel
|
|
922
|
+
function worldToScreen(wx, wy) {
|
|
923
|
+
if (mm.tiles && mm.tileW > 0) {
|
|
924
|
+
// Tile-based — wx/wy are tile coords
|
|
925
|
+
return [(x + wx * mm.tileScale) | 0, (y + wy * mm.tileScale) | 0];
|
|
926
|
+
}
|
|
927
|
+
// World-space normalised
|
|
928
|
+
let nx, ny;
|
|
929
|
+
if (mm.follow) {
|
|
930
|
+
// Center on follow entity
|
|
931
|
+
nx = 0.5 + (wx - mm.follow.x) / mm.worldW;
|
|
932
|
+
ny = 0.5 + (wy - mm.follow.y) / mm.worldH;
|
|
933
|
+
} else {
|
|
934
|
+
nx = wx / mm.worldW;
|
|
935
|
+
ny = wy / mm.worldH;
|
|
936
|
+
}
|
|
937
|
+
return [(x + nx * mw) | 0, (y + ny * mh) | 0];
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// 5. Entity dots
|
|
941
|
+
for (const e of mm.entities) {
|
|
942
|
+
const [ex, ey] = worldToScreen(e.x, e.y);
|
|
943
|
+
const dotSize = e.size ?? 2;
|
|
944
|
+
if (ex < x - dotSize || ex >= x + mw + dotSize || ey < y - dotSize || ey >= y + mh + dotSize)
|
|
945
|
+
continue;
|
|
753
946
|
const ec = _unpack(e.color ?? rgba8(255, 255, 255));
|
|
754
|
-
|
|
755
|
-
for (let
|
|
756
|
-
for (let
|
|
947
|
+
const half = (dotSize / 2) | 0;
|
|
948
|
+
for (let dy = -half; dy < dotSize - half; dy++) {
|
|
949
|
+
for (let dx = -half; dx < dotSize - half; dx++) {
|
|
950
|
+
const px = ex + dx,
|
|
951
|
+
py = ey + dy;
|
|
952
|
+
if (px < x || px >= x + mw || py < y || py >= y + mh) continue;
|
|
953
|
+
if (isCircle && !_inCircle(px, py, cx, cy, radius)) continue;
|
|
954
|
+
_blend(px, py, ec.r, ec.g, ec.b, ec.a);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// 6. Player marker (with optional blink)
|
|
960
|
+
if (mm.player) {
|
|
961
|
+
const blink = mm.player.blink !== false;
|
|
962
|
+
const visible = !blink || Math.sin(time * 8) > 0;
|
|
963
|
+
if (visible) {
|
|
964
|
+
const [px, py] = worldToScreen(mm.player.x, mm.player.y);
|
|
965
|
+
const pc = _unpack(mm.player.color ?? rgba8(50, 150, 255));
|
|
966
|
+
const ps = mm.player.size ?? 3;
|
|
967
|
+
const half = (ps / 2) | 0;
|
|
968
|
+
for (let dy = -half; dy < ps - half; dy++) {
|
|
969
|
+
for (let dx = -half; dx < ps - half; dx++) {
|
|
970
|
+
const ppx = px + dx,
|
|
971
|
+
ppy = py + dy;
|
|
972
|
+
if (ppx < x || ppx >= x + mw || ppy < y || ppy >= y + mh) continue;
|
|
973
|
+
if (isCircle && !_inCircle(ppx, ppy, cx, cy, radius)) continue;
|
|
974
|
+
_blend(ppx, ppy, pc.r, pc.g, pc.b, pc.a);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// 7. Border
|
|
981
|
+
if (mm.borderLight !== null) {
|
|
982
|
+
if (isCircle) {
|
|
983
|
+
// Circle border — draw a ring
|
|
984
|
+
const bc = _unpack(mm.borderLight);
|
|
985
|
+
const r2inner = (radius - 1) * (radius - 1);
|
|
986
|
+
const r2outer = radius * radius;
|
|
987
|
+
for (let py = y; py < y + mh; py++) {
|
|
988
|
+
for (let px = x; px < x + mw; px++) {
|
|
989
|
+
const dx = px - cx,
|
|
990
|
+
dy = py - cy;
|
|
991
|
+
const d2 = dx * dx + dy * dy;
|
|
992
|
+
if (d2 >= r2inner && d2 <= r2outer) {
|
|
993
|
+
_blend(px, py, bc.r, bc.g, bc.b, bc.a);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
998
|
+
drawPixelBorder(x, y, mw, mh, mm.borderLight, mm.borderDark, 1);
|
|
999
|
+
}
|
|
757
1000
|
}
|
|
758
1001
|
}
|
|
759
1002
|
|
|
@@ -768,6 +1011,40 @@ export function api2d(gpu) {
|
|
|
768
1011
|
_print(text, x, y, color, scale);
|
|
769
1012
|
}
|
|
770
1013
|
|
|
1014
|
+
/**
|
|
1015
|
+
* drawFloatingTexts(system, offsetX?, offsetY?)
|
|
1016
|
+
* Render all active texts from a createFloatingTextSystem().
|
|
1017
|
+
* offsetX/offsetY allow camera offset (for screen shake, etc.)
|
|
1018
|
+
*/
|
|
1019
|
+
function drawFloatingTexts(system, offsetX = 0, offsetY = 0) {
|
|
1020
|
+
const texts = system.getTexts();
|
|
1021
|
+
for (const t of texts) {
|
|
1022
|
+
const alpha = Math.min(255, Math.floor((t.timer / t.maxTimer) * 255));
|
|
1023
|
+
const r = (t.color >> 16) & 0xff;
|
|
1024
|
+
const g = (t.color >> 8) & 0xff;
|
|
1025
|
+
const b = t.color & 0xff;
|
|
1026
|
+
_print(t.text, (t.x + offsetX) | 0, (t.y + offsetY) | 0, rgba8(r, g, b, alpha), t.scale);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* drawFloatingTexts3D(system, projectFn)
|
|
1032
|
+
* Render 3D floating texts using a user-supplied projection function.
|
|
1033
|
+
* projectFn(x, y, z) should return [screenX, screenY].
|
|
1034
|
+
* Use with spawn(..., { z: worldZ }) for 3D world-space floating texts.
|
|
1035
|
+
*/
|
|
1036
|
+
function drawFloatingTexts3D(system, projectFn) {
|
|
1037
|
+
const texts = system.getTexts();
|
|
1038
|
+
for (const t of texts) {
|
|
1039
|
+
const [sx, sy] = projectFn(t.x, t.y, t.z ?? 0);
|
|
1040
|
+
const alpha = Math.min(255, Math.floor((t.timer / t.maxTimer) * 255));
|
|
1041
|
+
const r = (t.color >> 16) & 0xff;
|
|
1042
|
+
const g = (t.color >> 8) & 0xff;
|
|
1043
|
+
const b = t.color & 0xff;
|
|
1044
|
+
_print(t.text, sx | 0, sy | 0, rgba8(r, g, b, alpha), t.scale);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
771
1048
|
// ── Full-screen helpers ───────────────────────────────────────────────────────
|
|
772
1049
|
|
|
773
1050
|
/** Full-screen vertical gradient — great for sky/title backgrounds */
|
|
@@ -870,7 +1147,10 @@ export function api2d(gpu) {
|
|
|
870
1147
|
drawPixelBorder,
|
|
871
1148
|
drawPanel,
|
|
872
1149
|
drawCrosshair,
|
|
1150
|
+
createMinimap,
|
|
873
1151
|
drawMinimap,
|
|
1152
|
+
drawFloatingTexts,
|
|
1153
|
+
drawFloatingTexts3D,
|
|
874
1154
|
scrollingText,
|
|
875
1155
|
});
|
|
876
1156
|
},
|
package/runtime/api-3d/pbr.js
CHANGED
|
@@ -65,5 +65,49 @@ export function pbrModule({ meshes }) {
|
|
|
65
65
|
return true;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Set PBR scalar properties on a mesh without requiring texture maps.
|
|
70
|
+
* Clones the material so the change only affects this specific mesh.
|
|
71
|
+
*
|
|
72
|
+
* @param {number} meshId - ID returned by createSphere / createCube etc.
|
|
73
|
+
* @param {object} [opts]
|
|
74
|
+
* @param {number} [opts.metalness] - 0 (dielectric) → 1 (fully metallic)
|
|
75
|
+
* @param {number} [opts.roughness] - 0 (mirror-smooth) → 1 (fully rough)
|
|
76
|
+
* @param {number} [opts.envMapIntensity] - Strength of environment reflections
|
|
77
|
+
* @param {number} [opts.color] - Override surface colour (hex)
|
|
78
|
+
* @returns {boolean} true on success
|
|
79
|
+
*/
|
|
80
|
+
function setPBRProperties(meshId, opts = {}) {
|
|
81
|
+
const mesh = meshes.get(meshId);
|
|
82
|
+
if (!mesh) return false;
|
|
83
|
+
|
|
84
|
+
// Replace with a fresh MeshStandardMaterial so we own it exclusively
|
|
85
|
+
// (avoids mutating a shared cached material that other meshes may use)
|
|
86
|
+
const old = mesh.material;
|
|
87
|
+
mesh.material = new THREE.MeshStandardMaterial({
|
|
88
|
+
color:
|
|
89
|
+
opts.color !== undefined
|
|
90
|
+
? new THREE.Color(opts.color)
|
|
91
|
+
: (old.color?.clone() ?? new THREE.Color(0xffffff)),
|
|
92
|
+
map: old.map ?? null,
|
|
93
|
+
metalness:
|
|
94
|
+
opts.metalness !== undefined
|
|
95
|
+
? Math.max(0, Math.min(1, opts.metalness))
|
|
96
|
+
: (old.metalness ?? 0.0),
|
|
97
|
+
roughness:
|
|
98
|
+
opts.roughness !== undefined
|
|
99
|
+
? Math.max(0, Math.min(1, opts.roughness))
|
|
100
|
+
: (old.roughness ?? 0.6),
|
|
101
|
+
envMapIntensity: opts.envMapIntensity ?? old.envMapIntensity ?? 1.0,
|
|
102
|
+
emissive: old.emissive?.clone() ?? new THREE.Color(0),
|
|
103
|
+
emissiveIntensity: old.emissiveIntensity ?? 0,
|
|
104
|
+
transparent: old.transparent ?? false,
|
|
105
|
+
opacity: old.opacity ?? 1.0,
|
|
106
|
+
side: old.side ?? THREE.FrontSide,
|
|
107
|
+
});
|
|
108
|
+
mesh.material.needsUpdate = true;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { loadNormalMap, setNormalMap, setPBRMaps, setPBRProperties };
|
|
69
113
|
}
|
package/runtime/api-3d.js
CHANGED
|
@@ -139,6 +139,7 @@ export function threeDApi(gpu) {
|
|
|
139
139
|
loadNormalMap: ctx.loadNormalMap,
|
|
140
140
|
setNormalMap: ctx.setNormalMap,
|
|
141
141
|
setPBRMaps: ctx.setPBRMaps,
|
|
142
|
+
setPBRProperties: ctx.setPBRProperties,
|
|
142
143
|
|
|
143
144
|
// GPU particle system
|
|
144
145
|
createParticleSystem: ctx.createParticleSystem,
|
package/runtime/api-effects.js
CHANGED
|
@@ -37,6 +37,58 @@ const ChromaticAberrationShader = {
|
|
|
37
37
|
`,
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
// Glitch/damage shader — scanline displacement, RGB split, block artifacts
|
|
41
|
+
const GlitchShader = {
|
|
42
|
+
uniforms: {
|
|
43
|
+
tDiffuse: { value: null },
|
|
44
|
+
intensity: { value: 0.0 },
|
|
45
|
+
time: { value: 0.0 },
|
|
46
|
+
},
|
|
47
|
+
vertexShader: `
|
|
48
|
+
varying vec2 vUv;
|
|
49
|
+
void main() {
|
|
50
|
+
vUv = uv;
|
|
51
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
fragmentShader: `
|
|
55
|
+
uniform sampler2D tDiffuse;
|
|
56
|
+
uniform float intensity;
|
|
57
|
+
uniform float time;
|
|
58
|
+
varying vec2 vUv;
|
|
59
|
+
|
|
60
|
+
float rand(vec2 co) {
|
|
61
|
+
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void main() {
|
|
65
|
+
vec2 uv = vUv;
|
|
66
|
+
|
|
67
|
+
// Scanline displacement — horizontal bands shift left/right
|
|
68
|
+
float scanJitter = step(0.99 - intensity * 0.3, rand(vec2(time * 1.3, floor(uv.y * 40.0))))
|
|
69
|
+
* (rand(vec2(time, floor(uv.y * 40.0))) - 0.5) * intensity * 0.15;
|
|
70
|
+
uv.x += scanJitter;
|
|
71
|
+
|
|
72
|
+
// Block glitch — large rectangular regions shift
|
|
73
|
+
float blockY = floor(uv.y * 8.0);
|
|
74
|
+
float blockShift = step(0.97 - intensity * 0.15, rand(vec2(blockY, time * 0.7)))
|
|
75
|
+
* (rand(vec2(blockY + 1.0, time)) - 0.5) * intensity * 0.1;
|
|
76
|
+
uv.x += blockShift;
|
|
77
|
+
|
|
78
|
+
// RGB channel split
|
|
79
|
+
float rgbShift = intensity * 0.012;
|
|
80
|
+
float r = texture2D(tDiffuse, vec2(uv.x + rgbShift, uv.y + rgbShift * 0.5)).r;
|
|
81
|
+
float g = texture2D(tDiffuse, uv).g;
|
|
82
|
+
float b = texture2D(tDiffuse, vec2(uv.x - rgbShift, uv.y - rgbShift * 0.3)).b;
|
|
83
|
+
|
|
84
|
+
// Color corruption — random bright pixels
|
|
85
|
+
float noise = step(0.995 - intensity * 0.05, rand(uv + time)) * intensity;
|
|
86
|
+
|
|
87
|
+
gl_FragColor = vec4(r + noise, g, b + noise * 0.5, 1.0);
|
|
88
|
+
}
|
|
89
|
+
`,
|
|
90
|
+
};
|
|
91
|
+
|
|
40
92
|
// Vignette shader
|
|
41
93
|
const VignetteShader = {
|
|
42
94
|
uniforms: {
|
|
@@ -81,9 +133,11 @@ export function effectsApi(gpu) {
|
|
|
81
133
|
let fxaaPass = null;
|
|
82
134
|
let chromaticAberrationPass = null;
|
|
83
135
|
let vignettePass = null;
|
|
136
|
+
let glitchPass = null;
|
|
84
137
|
|
|
85
138
|
// Effect states
|
|
86
139
|
let effectsEnabled = false;
|
|
140
|
+
let glitchTime = 0;
|
|
87
141
|
|
|
88
142
|
// Custom shader materials
|
|
89
143
|
const customShaders = new Map();
|
|
@@ -102,7 +156,7 @@ export function effectsApi(gpu) {
|
|
|
102
156
|
}
|
|
103
157
|
|
|
104
158
|
// === BLOOM EFFECTS ===
|
|
105
|
-
function enableBloom(strength = 1.0, radius = 0.5, threshold = 0.
|
|
159
|
+
function enableBloom(strength = 1.0, radius = 0.5, threshold = 0.6) {
|
|
106
160
|
initPostProcessing();
|
|
107
161
|
|
|
108
162
|
if (bloomPass) {
|
|
@@ -187,7 +241,7 @@ export function effectsApi(gpu) {
|
|
|
187
241
|
}
|
|
188
242
|
|
|
189
243
|
// === VIGNETTE ===
|
|
190
|
-
function enableVignette(darkness = 1.
|
|
244
|
+
function enableVignette(darkness = 1.0, offset = 0.9) {
|
|
191
245
|
initPostProcessing();
|
|
192
246
|
if (vignettePass) {
|
|
193
247
|
vignettePass.uniforms['darkness'].value = darkness;
|
|
@@ -207,6 +261,31 @@ export function effectsApi(gpu) {
|
|
|
207
261
|
}
|
|
208
262
|
}
|
|
209
263
|
|
|
264
|
+
// === GLITCH EFFECT ===
|
|
265
|
+
function enableGlitch(intensity = 0.5) {
|
|
266
|
+
initPostProcessing();
|
|
267
|
+
if (glitchPass) {
|
|
268
|
+
glitchPass.uniforms['intensity'].value = Math.max(0, Math.min(1, intensity));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
glitchPass = new ShaderPass(GlitchShader);
|
|
272
|
+
glitchPass.uniforms['intensity'].value = Math.max(0, Math.min(1, intensity));
|
|
273
|
+
composer.addPass(glitchPass);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function disableGlitch() {
|
|
277
|
+
if (glitchPass && composer) {
|
|
278
|
+
composer.removePass(glitchPass);
|
|
279
|
+
glitchPass = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function setGlitchIntensity(intensity) {
|
|
284
|
+
if (glitchPass) {
|
|
285
|
+
glitchPass.uniforms['intensity'].value = Math.max(0, Math.min(1, intensity));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
210
289
|
// === CUSTOM SHADERS ===
|
|
211
290
|
|
|
212
291
|
// Holographic shader
|
|
@@ -651,7 +730,7 @@ export function effectsApi(gpu) {
|
|
|
651
730
|
const bloom = opts.bloom !== undefined ? opts.bloom : {};
|
|
652
731
|
if (bloom !== false) {
|
|
653
732
|
const b = typeof bloom === 'object' ? bloom : {};
|
|
654
|
-
enableBloom(b.strength ?? 1.
|
|
733
|
+
enableBloom(b.strength ?? 1.0, b.radius ?? 0.4, b.threshold ?? 0.6);
|
|
655
734
|
}
|
|
656
735
|
|
|
657
736
|
// FXAA
|
|
@@ -701,6 +780,11 @@ export function effectsApi(gpu) {
|
|
|
701
780
|
// Update effects (called every frame)
|
|
702
781
|
function updateEffects(deltaTime) {
|
|
703
782
|
updateShaderTime(deltaTime);
|
|
783
|
+
// Animate glitch time uniform for randomness
|
|
784
|
+
if (glitchPass) {
|
|
785
|
+
glitchTime += deltaTime;
|
|
786
|
+
glitchPass.uniforms['time'].value = glitchTime;
|
|
787
|
+
}
|
|
704
788
|
}
|
|
705
789
|
|
|
706
790
|
// === EXPOSE API ===
|
|
@@ -719,6 +803,9 @@ export function effectsApi(gpu) {
|
|
|
719
803
|
disableChromaticAberration: disableChromaticAberration,
|
|
720
804
|
enableVignette: enableVignette,
|
|
721
805
|
disableVignette: disableVignette,
|
|
806
|
+
enableGlitch: enableGlitch,
|
|
807
|
+
disableGlitch: disableGlitch,
|
|
808
|
+
setGlitchIntensity: setGlitchIntensity,
|
|
722
809
|
|
|
723
810
|
// Convenience
|
|
724
811
|
enableRetroEffects: enableRetroEffects,
|