@jgengine/shell 0.4.0 → 0.6.0

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 (62) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +27 -0
  3. package/dist/GamePlayerShell.js +19 -9
  4. package/dist/demo/demoGame.js +10 -2
  5. package/dist/demo/environmentShowcase.d.ts +2 -0
  6. package/dist/demo/environmentShowcase.js +15 -0
  7. package/dist/environment/EnvironmentScene.d.ts +5 -0
  8. package/dist/environment/EnvironmentScene.js +75 -0
  9. package/dist/environment/index.d.ts +1 -0
  10. package/dist/environment/index.js +1 -0
  11. package/dist/environment.d.ts +1 -0
  12. package/dist/environment.js +1 -0
  13. package/dist/registry.d.ts +4 -2
  14. package/dist/structures/GeneratedBuilding.d.ts +44 -0
  15. package/dist/structures/GeneratedBuilding.js +86 -0
  16. package/dist/terrain/GrassField.d.ts +21 -0
  17. package/dist/terrain/GrassField.js +31 -0
  18. package/dist/terrain/ProceduralGround.d.ts +9 -0
  19. package/dist/terrain/ProceduralGround.js +17 -0
  20. package/dist/terrain/grassGeometry.d.ts +27 -0
  21. package/dist/terrain/grassGeometry.js +77 -0
  22. package/dist/terrain/grassMaterial.d.ts +33 -0
  23. package/dist/terrain/grassMaterial.js +108 -0
  24. package/dist/terrain/index.d.ts +7 -0
  25. package/dist/terrain/index.js +7 -0
  26. package/dist/terrain/random.d.ts +4 -0
  27. package/dist/terrain/random.js +27 -0
  28. package/dist/terrain/terrainMath.d.ts +34 -0
  29. package/dist/terrain/terrainMath.js +92 -0
  30. package/dist/terrain.d.ts +1 -0
  31. package/dist/terrain.js +1 -0
  32. package/dist/water/Ocean.d.ts +6 -0
  33. package/dist/water/Ocean.js +30 -0
  34. package/dist/water/OceanConfig.d.ts +90 -0
  35. package/dist/water/OceanConfig.js +131 -0
  36. package/dist/water/OceanMaterial.d.ts +54 -0
  37. package/dist/water/OceanMaterial.js +51 -0
  38. package/dist/water/OceanShader.d.ts +2 -0
  39. package/dist/water/OceanShader.js +78 -0
  40. package/dist/water/index.d.ts +3 -0
  41. package/dist/water/index.js +3 -0
  42. package/dist/water.d.ts +1 -0
  43. package/dist/water.js +1 -0
  44. package/dist/weather/LightningStrike.d.ts +17 -0
  45. package/dist/weather/LightningStrike.js +130 -0
  46. package/dist/weather/RainField.d.ts +20 -0
  47. package/dist/weather/RainField.js +107 -0
  48. package/dist/weather/SnowField.d.ts +19 -0
  49. package/dist/weather/SnowField.js +108 -0
  50. package/dist/weather/WeatherLayer.d.ts +17 -0
  51. package/dist/weather/WeatherLayer.js +16 -0
  52. package/dist/weather/index.d.ts +5 -0
  53. package/dist/weather/index.js +5 -0
  54. package/dist/weather/weatherGeometry.d.ts +2 -0
  55. package/dist/weather/weatherGeometry.js +14 -0
  56. package/dist/weather/weatherMath.d.ts +7 -0
  57. package/dist/weather/weatherMath.js +30 -0
  58. package/dist/weather/weatherUniforms.d.ts +18 -0
  59. package/dist/weather/weatherUniforms.js +34 -0
  60. package/dist/world/InstancedBodies.d.ts +17 -0
  61. package/dist/world/InstancedBodies.js +84 -0
  62. package/package.json +12 -5
@@ -0,0 +1,14 @@
1
+ import * as THREE from "three";
2
+ import { createWeatherSeedAttributes } from "./weatherMath.js";
3
+ export function createWeatherQuadGeometry(maxCount, seed) {
4
+ const count = Math.max(0, Math.floor(maxCount));
5
+ const geometry = new THREE.InstancedBufferGeometry();
6
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]), 3));
7
+ geometry.setAttribute("uv", new THREE.BufferAttribute(new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), 2));
8
+ geometry.setIndex([0, 1, 2, 0, 2, 3]);
9
+ const attributes = createWeatherSeedAttributes(count, seed);
10
+ geometry.setAttribute("aSpawn", new THREE.InstancedBufferAttribute(attributes.spawn, 3));
11
+ geometry.setAttribute("aDrift", new THREE.InstancedBufferAttribute(attributes.drift, 1));
12
+ geometry.instanceCount = count;
13
+ return geometry;
14
+ }
@@ -0,0 +1,7 @@
1
+ export declare function clampWeatherRatio(value: number): number;
2
+ export declare function resolveWeatherInstanceCount(maxCount: number, density: number): number;
3
+ export interface WeatherSeedAttributes {
4
+ spawn: Float32Array;
5
+ drift: Float32Array;
6
+ }
7
+ export declare function createWeatherSeedAttributes(maxCount: number, seed: number): WeatherSeedAttributes;
@@ -0,0 +1,30 @@
1
+ export function clampWeatherRatio(value) {
2
+ if (!Number.isFinite(value))
3
+ return 0;
4
+ if (value <= 0)
5
+ return 0;
6
+ if (value >= 1)
7
+ return 1;
8
+ return value;
9
+ }
10
+ export function resolveWeatherInstanceCount(maxCount, density) {
11
+ const safeMax = Math.max(0, Math.floor(maxCount));
12
+ return Math.floor(safeMax * clampWeatherRatio(density));
13
+ }
14
+ export function createWeatherSeedAttributes(maxCount, seed) {
15
+ const count = Math.max(0, Math.floor(maxCount));
16
+ const spawn = new Float32Array(count * 3);
17
+ const drift = new Float32Array(count);
18
+ let state = seed >>> 0;
19
+ const next = () => {
20
+ state = (state * 1664525 + 1013904223) >>> 0;
21
+ return state / 4294967296;
22
+ };
23
+ for (let index = 0; index < count; index += 1) {
24
+ spawn[index * 3] = next();
25
+ spawn[index * 3 + 1] = next();
26
+ spawn[index * 3 + 2] = next();
27
+ drift[index] = next();
28
+ }
29
+ return { spawn, drift };
30
+ }
@@ -0,0 +1,18 @@
1
+ import { type ReactNode } from "react";
2
+ import * as THREE from "three";
3
+ export type WeatherVector = readonly [number, number, number];
4
+ export interface WeatherUniformSet {
5
+ time: THREE.IUniform<number>;
6
+ wind: THREE.IUniform<THREE.Vector3>;
7
+ lightning: THREE.IUniform<number>;
8
+ }
9
+ export interface WeatherUniformOptions {
10
+ wind?: WeatherVector;
11
+ lightning?: number;
12
+ timeScale?: number;
13
+ }
14
+ export declare function createWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
15
+ export declare function WeatherUniformProvider({ children, ...options }: WeatherUniformOptions & {
16
+ children: ReactNode;
17
+ }): import("react").JSX.Element;
18
+ export declare function useWeatherUniformSet(options?: WeatherUniformOptions): WeatherUniformSet;
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import * as THREE from "three";
5
+ const WeatherUniformContext = createContext(null);
6
+ export function createWeatherUniformSet(options = {}) {
7
+ const wind = options.wind ?? [0, 0, 0];
8
+ return {
9
+ time: { value: 0 },
10
+ wind: { value: new THREE.Vector3(wind[0], wind[1], wind[2]) },
11
+ lightning: { value: options.lightning ?? 0 },
12
+ };
13
+ }
14
+ function useUniformTicker(uniforms, options, enabled = true) {
15
+ useFrame((_state, delta) => {
16
+ if (!enabled)
17
+ return;
18
+ const wind = options.wind ?? [0, 0, 0];
19
+ uniforms.time.value += delta * (options.timeScale ?? 1);
20
+ uniforms.wind.value.set(wind[0], wind[1], wind[2]);
21
+ uniforms.lightning.value = options.lightning ?? 0;
22
+ });
23
+ }
24
+ export function WeatherUniformProvider({ children, ...options }) {
25
+ const uniforms = useMemo(() => createWeatherUniformSet(options), []);
26
+ useUniformTicker(uniforms, options);
27
+ return _jsx(WeatherUniformContext.Provider, { value: uniforms, children: children });
28
+ }
29
+ export function useWeatherUniformSet(options = {}) {
30
+ const shared = useContext(WeatherUniformContext);
31
+ const local = useMemo(() => createWeatherUniformSet(options), []);
32
+ useUniformTicker(local, options, shared === null);
33
+ return shared ?? local;
34
+ }
@@ -0,0 +1,17 @@
1
+ import type { PhysicsWorld } from "@jgengine/core/physics/physicsWorld";
2
+ export interface InstancedBodiesProps {
3
+ /** Physics world whose SoA buffers are streamed straight into the instance matrix. */
4
+ world: PhysicsWorld;
5
+ /** When true, color each instance by simulation state (sleeping / awake / contact) instead of its base color. */
6
+ debugTint?: boolean;
7
+ /** Per-body base RGB (length ≥ capacity·3), used when debugTint is off. Defaults to flat gray. */
8
+ baseColors?: Float32Array;
9
+ /** Bump to force a color re-upload after the world is rebuilt (e.g. reset). */
10
+ epoch?: number;
11
+ }
12
+ /**
13
+ * Renders a PhysicsWorld's box bodies as a single InstancedMesh — one draw call per batch.
14
+ * Transforms are written directly into `instanceMatrix.array` each frame (bodies never touch
15
+ * the per-entity React path). Reusable by any game: hand it a physics world and go.
16
+ */
17
+ export declare function InstancedBodies({ world, debugTint, baseColors, epoch }: InstancedBodiesProps): import("react").JSX.Element;
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useFrame } from "@react-three/fiber";
3
+ import { useEffect, useMemo, useRef } from "react";
4
+ import * as THREE from "three";
5
+ const SLEEPING_COLOR = new THREE.Color(0.32, 0.4, 0.62);
6
+ const AWAKE_COLOR = new THREE.Color(0.45, 0.78, 0.5);
7
+ const CONTACT_COLOR = new THREE.Color(0.96, 0.55, 0.18);
8
+ const DEFAULT_BASE = new THREE.Color(0.62, 0.64, 0.68);
9
+ /**
10
+ * Renders a PhysicsWorld's box bodies as a single InstancedMesh — one draw call per batch.
11
+ * Transforms are written directly into `instanceMatrix.array` each frame (bodies never touch
12
+ * the per-entity React path). Reusable by any game: hand it a physics world and go.
13
+ */
14
+ export function InstancedBodies({ world, debugTint = false, baseColors, epoch = 0 }) {
15
+ const meshRef = useRef(null);
16
+ const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
17
+ const material = useMemo(() => new THREE.MeshStandardMaterial({ roughness: 0.85, metalness: 0, vertexColors: false }), []);
18
+ const capacity = world.capacity;
19
+ const instanceColor = useMemo(() => new THREE.InstancedBufferAttribute(new Float32Array(capacity * 3), 3), [capacity]);
20
+ useEffect(() => {
21
+ const mesh = meshRef.current;
22
+ if (mesh === null)
23
+ return;
24
+ mesh.instanceColor = instanceColor;
25
+ mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
26
+ instanceColor.setUsage(THREE.DynamicDrawUsage);
27
+ }, [instanceColor]);
28
+ const lastTintRef = useRef(null);
29
+ useEffect(() => {
30
+ lastTintRef.current = null;
31
+ }, [epoch, debugTint]);
32
+ useFrame(() => {
33
+ const mesh = meshRef.current;
34
+ if (mesh === null)
35
+ return;
36
+ const count = world.count;
37
+ mesh.count = count;
38
+ const m = mesh.instanceMatrix.array;
39
+ const { posX, posY, posZ, halfX, halfY, halfZ } = world;
40
+ for (let i = 0; i < count; i += 1) {
41
+ const o = i * 16;
42
+ m[o] = halfX[i] * 2;
43
+ m[o + 5] = halfY[i] * 2;
44
+ m[o + 10] = halfZ[i] * 2;
45
+ m[o + 12] = posX[i];
46
+ m[o + 13] = posY[i];
47
+ m[o + 14] = posZ[i];
48
+ m[o + 15] = 1;
49
+ }
50
+ mesh.instanceMatrix.needsUpdate = true;
51
+ const c = instanceColor.array;
52
+ if (debugTint) {
53
+ const { contact } = world;
54
+ for (let i = 0; i < count; i += 1) {
55
+ const col = contact[i] !== 0 ? CONTACT_COLOR : world.isSleeping(i) ? SLEEPING_COLOR : AWAKE_COLOR;
56
+ const o = i * 3;
57
+ c[o] = col.r;
58
+ c[o + 1] = col.g;
59
+ c[o + 2] = col.b;
60
+ }
61
+ instanceColor.needsUpdate = true;
62
+ }
63
+ else if (lastTintRef.current !== false) {
64
+ if (baseColors !== undefined) {
65
+ c.set(baseColors.subarray(0, capacity * 3));
66
+ }
67
+ else {
68
+ for (let i = 0; i < capacity; i += 1) {
69
+ const o = i * 3;
70
+ c[o] = DEFAULT_BASE.r;
71
+ c[o + 1] = DEFAULT_BASE.g;
72
+ c[o + 2] = DEFAULT_BASE.b;
73
+ }
74
+ }
75
+ instanceColor.needsUpdate = true;
76
+ }
77
+ lastTintRef.current = debugTint;
78
+ });
79
+ useEffect(() => () => {
80
+ geometry.dispose();
81
+ material.dispose();
82
+ }, [geometry, material]);
83
+ return (_jsx("instancedMesh", { ref: meshRef, args: [geometry, material, capacity], frustumCulled: false, castShadow: false, receiveShadow: false }));
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/shell",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Game player shell for JGengine: React Three Fiber canvas, orbit camera, input tracking, HUD mounting, GameUiPreview, and a demo game. Consumers supply a GameRegistry.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
@@ -10,8 +10,15 @@
10
10
  "url": "git+https://github.com/Noisemaker111/jgengine.git",
11
11
  "directory": "packages/shell"
12
12
  },
13
- "files": ["dist"],
13
+ "files": [
14
+ "dist",
15
+ "CHANGELOG.md"
16
+ ],
14
17
  "exports": {
18
+ "./weather": {
19
+ "types": "./dist/weather/index.d.ts",
20
+ "default": "./dist/weather/index.js"
21
+ },
15
22
  "./*": {
16
23
  "types": "./dist/*.d.ts",
17
24
  "default": "./dist/*.js"
@@ -23,9 +30,9 @@
23
30
  "test": "bun test src"
24
31
  },
25
32
  "dependencies": {
26
- "@jgengine/core": "^0.4.0",
27
- "@jgengine/react": "^0.4.0",
28
- "@jgengine/ws": "^0.4.0"
33
+ "@jgengine/core": "^0.6.0",
34
+ "@jgengine/react": "^0.6.0",
35
+ "@jgengine/ws": "^0.6.0"
29
36
  },
30
37
  "peerDependencies": {
31
38
  "@react-three/drei": "^10.0.0",