@adatechnology/game-fluids-three 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +105 -2
- package/dist/index.d.ts +68 -0
- package/dist/index.global.js +1117 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +1081 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson Fernandes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @adatechnology/game-fluids-three
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
three.js renderer for [`@adatechnology/game-fluids`](../game-fluids). It only reads the simulation state, so it draws exactly what the
|
|
4
|
+
deterministic world says (a LAN client with the same state draws the same liquids). `three` is a peer dependency used for types only: you pass your
|
|
5
|
+
own `THREE` instance at runtime.
|
|
6
|
+
|
|
7
|
+
What it draws:
|
|
8
|
+
|
|
9
|
+
- **Liquid in containers**: inside every bound object, the container's inner shape clipped by the world-space liquid surface, so the top is always
|
|
10
|
+
on the surface plane whatever the tilt (upright, lying down, upside down). One material per liquid (colour, opacity, roughness from gloss).
|
|
11
|
+
Steam puffs rise from a hot surface.
|
|
12
|
+
- **Streams**: a thread along the ballistic path (tube updated in place) that narrows as it accelerates, with a drop that grows at the leading tip
|
|
13
|
+
and a trailing drop when it ends; thin streams are a sequence of falling drops.
|
|
14
|
+
- **Stains**: a smooth fan from the 16 flow-front radii (never a perfect disc), lying on the surface plane a hair above it
|
|
15
|
+
(`polygonOffset`, oriented by the stain's normal), tinted by the liquid with a soft rim, plus an inner glossy fan at `wetRadius` that reflects
|
|
16
|
+
the environment and recedes while it dries. A dried mark is a faint matte patch.
|
|
17
|
+
- **Splashes**: pooled round droplets (`THREE.Points` with a small `ShaderMaterial`, the round sprite is drawn in the fragment shader, no textures).
|
|
18
|
+
|
|
19
|
+
Lit with the standard material, no transmission, a handful of draw calls, fixed budgets per quality.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pnpm add @adatechnology/game-fluids-three @adatechnology/game-fluids three
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Browser without a bundler (the global is `GameFluidsThree`; it bundles the few `game-fluids` helpers it calls, you still load `GameFluids` to create the world):
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script src="https://unpkg.com/three/build/three.min.js"></script>
|
|
31
|
+
<script src="https://unpkg.com/@adatechnology/game-fluids/dist/index.global.js"></script>
|
|
32
|
+
<script src="https://unpkg.com/@adatechnology/game-fluids-three/dist/index.global.js"></script>
|
|
33
|
+
<script>
|
|
34
|
+
const fluids = GameFluids.createFluids({ seed: 1 })
|
|
35
|
+
const view = GameFluidsThree.createFluidRenderer(THREE, { scene, fluids })
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import * as THREE from 'three'
|
|
43
|
+
import { createFluids } from '@adatechnology/game-fluids'
|
|
44
|
+
import { createFluidRenderer } from '@adatechnology/game-fluids-three'
|
|
45
|
+
|
|
46
|
+
const fluids = createFluids({ seed: 7 })
|
|
47
|
+
fluids.addSurface({ id: 'floor', point: [0, 0, 0], normal: [0, 1, 0], halfSize: [10, 10], porosity: 0.05 })
|
|
48
|
+
fluids.addContainer(
|
|
49
|
+
{ capacity: 0.25, liquid: 'coffee', liters: 0.25, temperature: 90, shape: { kind: 'cylinder', radius: 0.04, height: 0.1 }, opening: 0.04 },
|
|
50
|
+
{ id: 'cup' },
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const view = createFluidRenderer(THREE, { scene, fluids, quality: 'medium', envMap })
|
|
54
|
+
view.bindContainer('cup', cupObject, { innerOffset: [0, 0.005, 0] }) // inner bottom centre in the object's space
|
|
55
|
+
|
|
56
|
+
// every frame
|
|
57
|
+
const p = cupObject.localToWorld(new THREE.Vector3(0, 0.005, 0)) // world position of the inner bottom centre
|
|
58
|
+
fluids.setTransform('cup', [p.x, p.y, p.z], [q.x, q.y, q.z, q.w])
|
|
59
|
+
fluids.update(dt)
|
|
60
|
+
view.update(dt)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`innerOffset` is the offset, in the bound object's space, of the origin of the container shape (the inner bottom centre). The transform given to
|
|
64
|
+
`fluids.setTransform` must be that point's world position and the object's world rotation. Objects are assumed to have uniform scale.
|
|
65
|
+
|
|
66
|
+
Destroying a container's object (for example from `@adatechnology/game-destruction`): call `view.unbindContainer(id)` and
|
|
67
|
+
`fluids.removeContainer(id)`; the stains already on the floor stay and dry on their own.
|
|
68
|
+
|
|
69
|
+
## API
|
|
70
|
+
|
|
71
|
+
`createFluidRenderer(THREE, options)` returns a `FluidRenderer`.
|
|
72
|
+
|
|
73
|
+
| Option | |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `scene` | Where the effects are added (one `fluid_renderer` group) |
|
|
76
|
+
| `fluids` | The `FluidWorld` to draw |
|
|
77
|
+
| `quality` | `'low'`, `'medium'` (default) or `'high'`: sets the mesh resolution and the budgets below |
|
|
78
|
+
| `envMap` | Environment map the wet surfaces reflect (otherwise `scene.environment` if the host set one) |
|
|
79
|
+
| `pointScale` | Pixel scale of the point sprites, about half the canvas height (default 600) |
|
|
80
|
+
|
|
81
|
+
| Method | |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `bindContainer(containerId, object3D, { innerOffset? })` | Draw the liquid of a world container inside the object |
|
|
84
|
+
| `unbindContainer(containerId)` | Remove that liquid |
|
|
85
|
+
| `update(dt)` | Advance the visuals; call it after `fluids.update(dt)` |
|
|
86
|
+
| `clear()` | Remove stains, streams and particles (bindings stay) |
|
|
87
|
+
| `dispose()` | Remove everything from the scene and free geometries and materials |
|
|
88
|
+
| `stats()` | `{ quality, containers, streams, stains, particles, triangles, budget }` |
|
|
89
|
+
|
|
90
|
+
Budgets: stains 24 / 48 / 96, streams 12 / 24 / 48 and particles 96 / 256 / 512 for low / medium / high. When the world has more stains than the
|
|
91
|
+
budget, the newest are drawn.
|
|
92
|
+
|
|
93
|
+
Types exported: `FluidRenderer`, `FluidRendererOptions`, `FluidRendererStats`, `BindOptions`, `FluidQuality`, `ThreeLib`. `smoothOutline(front, subdiv)` is
|
|
94
|
+
exported too (the periodic Catmull-Rom used for the stain rim).
|
|
95
|
+
|
|
96
|
+
## Português
|
|
97
|
+
|
|
98
|
+
Renderizador three.js do `@adatechnology/game-fluids`: o líquido dentro do recipiente (um volume cortado pelo plano da superfície, sempre
|
|
99
|
+
horizontal no mundo), fios de líquido com gota na ponta, manchas que crescem a partir de uma frente de fluxo (nunca um disco perfeito), brilho de
|
|
100
|
+
superfície molhada que reflete o cenário e seca da borda para dentro, vapor e gotas de respingo. Só lê o estado da simulação, então é igual em LAN.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const view = createFluidRenderer(THREE, { scene, fluids, quality: 'medium', envMap })
|
|
104
|
+
view.bindContainer('copo', objetoDoCopo, { innerOffset: [0, 0.005, 0] })
|
|
105
|
+
// a cada frame: fluids.setTransform(...); fluids.update(dt); view.update(dt)
|
|
106
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as THREE_NS from 'three';
|
|
2
|
+
import { Vec3, FluidWorld } from '@adatechnology/game-fluids';
|
|
3
|
+
|
|
4
|
+
/** The only three.js members the renderer needs at runtime. Pass your own `THREE` instance (the whole namespace works). */
|
|
5
|
+
type ThreeLib = Pick<typeof THREE_NS, 'BufferAttribute' | 'BufferGeometry' | 'Color' | 'DoubleSide' | 'DynamicDrawUsage' | 'Group' | 'InstancedMesh' | 'Matrix4' | 'Mesh' | 'MeshStandardMaterial' | 'Points' | 'ShaderMaterial' | 'SphereGeometry' | 'Vector3'>;
|
|
6
|
+
/** Detail level. It sets the budgets (stains, streams, particles) and the mesh resolution. */
|
|
7
|
+
type FluidQuality = 'low' | 'medium' | 'high';
|
|
8
|
+
interface FluidRendererOptions {
|
|
9
|
+
/** Where the effects live (stains, streams, particles are added under one group). */
|
|
10
|
+
scene: THREE_NS.Object3D;
|
|
11
|
+
/** The simulation to draw. The renderer only reads its state and listens to `splash` events: it never changes the world. */
|
|
12
|
+
fluids: FluidWorld;
|
|
13
|
+
/** Default `medium`. */
|
|
14
|
+
quality?: FluidQuality;
|
|
15
|
+
/** Environment map the wet surfaces and liquids reflect. When omitted they use `scene.environment` if the host set one. */
|
|
16
|
+
envMap?: THREE_NS.Texture | null;
|
|
17
|
+
/** Pixel scale of the round point sprites (about half the canvas height in pixels). Default 600. */
|
|
18
|
+
pointScale?: number;
|
|
19
|
+
}
|
|
20
|
+
interface BindOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Offset, in the bound object's space, of the container's inner bottom centre (the origin of the shape given to `addContainer`).
|
|
23
|
+
* The world transform you give `fluids.setTransform` must be that point's world position and the object's rotation. Default `[0, 0, 0]`.
|
|
24
|
+
*/
|
|
25
|
+
innerOffset?: Vec3;
|
|
26
|
+
}
|
|
27
|
+
/** Counts of what is alive right now, plus the budgets of the chosen quality. */
|
|
28
|
+
interface FluidRendererStats {
|
|
29
|
+
quality: FluidQuality;
|
|
30
|
+
/** Bound containers with a visible liquid volume. */
|
|
31
|
+
containers: number;
|
|
32
|
+
/** Streams drawn, including the ones that are breaking into drops after the world ended them. */
|
|
33
|
+
streams: number;
|
|
34
|
+
/** Stain meshes in use (a stain uses an outer film and an inner glossy wet fan). */
|
|
35
|
+
stains: number;
|
|
36
|
+
/** Droplet and steam particles alive. */
|
|
37
|
+
particles: number;
|
|
38
|
+
/** Triangles currently drawn by the fluids. */
|
|
39
|
+
triangles: number;
|
|
40
|
+
budget: {
|
|
41
|
+
stains: number;
|
|
42
|
+
streams: number;
|
|
43
|
+
particles: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
interface FluidRenderer {
|
|
47
|
+
/** Draw the liquid of a world container inside `object` (a volume clipped by the liquid surface, plus steam). */
|
|
48
|
+
bindContainer(containerId: string, object: THREE_NS.Object3D, options?: BindOptions): void;
|
|
49
|
+
unbindContainer(containerId: string): void;
|
|
50
|
+
/** Advance the visuals (call it after `fluids.update(dt)`). */
|
|
51
|
+
update(dt: number): void;
|
|
52
|
+
/** Remove every stain, stream and particle (the bindings stay). */
|
|
53
|
+
clear(): void;
|
|
54
|
+
/** Remove everything from the scene and release geometries and materials. */
|
|
55
|
+
dispose(): void;
|
|
56
|
+
stats(): FluidRendererStats;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Draws a `FluidWorld` with three.js. It only reads the world state (plus `splash` events for droplets), so a LAN client that receives the
|
|
61
|
+
* same world state draws the same liquids.
|
|
62
|
+
*/
|
|
63
|
+
declare function createFluidRenderer(THREE: ThreeLib, options: FluidRendererOptions): FluidRenderer;
|
|
64
|
+
|
|
65
|
+
/** Periodic Catmull-Rom of the 16 front radii: a smooth outline with `subdiv` points per sample. */
|
|
66
|
+
declare const smoothOutline: (front: number[], subdiv: number, out?: number[]) => number[];
|
|
67
|
+
|
|
68
|
+
export { type BindOptions, type FluidQuality, type FluidRenderer, type FluidRendererOptions, type FluidRendererStats, type ThreeLib, createFluidRenderer, smoothOutline };
|