@guinetik/gcanvas 1.0.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/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lava Lamp - Generative Art Demo
|
|
3
|
+
*
|
|
4
|
+
* Fullscreen metaball lava lamp with realistic heat physics.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Game, ImageGo, Painter, ToggleButton, applyAnchor, Position } from "../../src/index.js";
|
|
8
|
+
import {
|
|
9
|
+
zoneTemperature,
|
|
10
|
+
thermalBuoyancy,
|
|
11
|
+
thermalGravity,
|
|
12
|
+
heatTransfer,
|
|
13
|
+
} from "../../src/math/heat.js";
|
|
14
|
+
|
|
15
|
+
// Configuration constants
|
|
16
|
+
const CONFIG = {
|
|
17
|
+
// Rendering
|
|
18
|
+
scaleFactor: 2,
|
|
19
|
+
blendMode: "screen",
|
|
20
|
+
|
|
21
|
+
// Physics
|
|
22
|
+
gravity: 0.000052,
|
|
23
|
+
buoyancyStrength: 0.00018,
|
|
24
|
+
maxSpeed: 0.00088,
|
|
25
|
+
dampingX: 0.995,
|
|
26
|
+
dampingY: 0.998,
|
|
27
|
+
|
|
28
|
+
// Temperature
|
|
29
|
+
tempRate: 0.008, // base rate for temperature changes
|
|
30
|
+
heatZoneY: 0.85, // heat zone at bottom
|
|
31
|
+
coolZoneY: 0.15, // cool zone at top
|
|
32
|
+
heatZoneMultiplier: 2.0, // fast heating at bottom
|
|
33
|
+
coolZoneMultiplier: 0.8, // gentle cooling - blobs linger at top
|
|
34
|
+
middleZoneMultiplier: 0.03, // slow transition in middle
|
|
35
|
+
transitionWidth: 0.25, // wide smooth transition between zones
|
|
36
|
+
heatTransferRate: 0.005, // faster blob-to-blob transfer (was 0.0022)
|
|
37
|
+
heatTransferRange: 1.8, // slightly larger transfer range
|
|
38
|
+
|
|
39
|
+
// Spawning
|
|
40
|
+
initialSpawnMin: 2,
|
|
41
|
+
initialSpawnRange: 2,
|
|
42
|
+
spawnMin: 3,
|
|
43
|
+
spawnRange: 3,
|
|
44
|
+
spawnOffsetY: 0.01,
|
|
45
|
+
spawnOffsetYRange: 0.01,
|
|
46
|
+
spawnOffsetX: 0.02,
|
|
47
|
+
initialRadius: 0.005,
|
|
48
|
+
targetRadiusMin: 0.04,
|
|
49
|
+
targetRadiusRange: 0.025,
|
|
50
|
+
growthRate: 0.0165,
|
|
51
|
+
growthThreshold: 0.7,
|
|
52
|
+
initialRiseVelocity: -0.00022,
|
|
53
|
+
maxBlobs: 5,
|
|
54
|
+
poolClearThreshold: 0.75,
|
|
55
|
+
|
|
56
|
+
// Drift & Chaos
|
|
57
|
+
driftSpeedMin: 0.165,
|
|
58
|
+
driftSpeedRange: 0.165,
|
|
59
|
+
driftAmountMin: 0.0000055,
|
|
60
|
+
driftAmountRange: 0.0000044,
|
|
61
|
+
wobbleForce: 0.000011,
|
|
62
|
+
wobbleZoneTop: 0.2,
|
|
63
|
+
wobbleZoneBottom: 0.7,
|
|
64
|
+
// Repulsion to prevent clustering
|
|
65
|
+
repulsionStrength: 0.00004, // How hard blobs push each other apart
|
|
66
|
+
repulsionRange: 2.5, // Multiplier of combined radii for repulsion
|
|
67
|
+
chaosStrength: 0.000008, // Random perturbation strength
|
|
68
|
+
chaosTempFactor: 1.5, // Hot blobs are more chaotic
|
|
69
|
+
|
|
70
|
+
// Boundaries
|
|
71
|
+
boundaryLeft: 0.1,
|
|
72
|
+
boundaryRight: 0.9,
|
|
73
|
+
boundaryTop: -0.1,
|
|
74
|
+
boundaryBounce: -0.3,
|
|
75
|
+
removalY: 1.15,
|
|
76
|
+
|
|
77
|
+
// Deformation
|
|
78
|
+
stretchBase: 0.75,
|
|
79
|
+
stretchTempFactor: 0.5,
|
|
80
|
+
wobble1Speed: 1.32,
|
|
81
|
+
wobble1Amount: 0.12,
|
|
82
|
+
wobble2Speed: 0.88,
|
|
83
|
+
wobble2PhaseFactor: 1.7,
|
|
84
|
+
wobble2Amount: 0.08,
|
|
85
|
+
|
|
86
|
+
// Rendering
|
|
87
|
+
metaballThreshold: 1.0,
|
|
88
|
+
edgeWidth: 0.15,
|
|
89
|
+
poolGlowStart: 0.7,
|
|
90
|
+
poolGlowBoost: 0.4,
|
|
91
|
+
brightnessBase: 0.35,
|
|
92
|
+
brightnessTempFactor: 1.0,
|
|
93
|
+
glowMax: 0.2,
|
|
94
|
+
glowFactor: 0.1,
|
|
95
|
+
glowR: 50,
|
|
96
|
+
glowG: 35,
|
|
97
|
+
glowB: 25,
|
|
98
|
+
|
|
99
|
+
// Lamp on/off
|
|
100
|
+
heatTransitionSpeed: 0.4, // How fast heat level changes
|
|
101
|
+
coolDownRate: 0.015, // How fast blobs cool when lamp is off
|
|
102
|
+
poolCoolRate: 0.008, // How fast the pool cools
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
class LavaLampDemo extends Game {
|
|
106
|
+
constructor(canvas) {
|
|
107
|
+
super(canvas);
|
|
108
|
+
this.backgroundColor = "#000";
|
|
109
|
+
this.enableFluidSize();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
init() {
|
|
113
|
+
super.init();
|
|
114
|
+
|
|
115
|
+
// Render resolution - proportional to screen, divided for performance
|
|
116
|
+
this.renderWidth = Math.floor(this.width / CONFIG.scaleFactor);
|
|
117
|
+
this.renderHeight = Math.floor(this.height / CONFIG.scaleFactor);
|
|
118
|
+
|
|
119
|
+
// Create ImageData for rendering
|
|
120
|
+
this.imageData = Painter.img.createImageData(
|
|
121
|
+
this.renderWidth,
|
|
122
|
+
this.renderHeight,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Create ImageGo to display
|
|
126
|
+
this.lavaImage = new ImageGo(this, this.imageData, {
|
|
127
|
+
x: 0,
|
|
128
|
+
y: 0,
|
|
129
|
+
width: this.width,
|
|
130
|
+
height: this.height,
|
|
131
|
+
anchor: "top-left",
|
|
132
|
+
});
|
|
133
|
+
this.pipeline.add(this.lavaImage);
|
|
134
|
+
|
|
135
|
+
// Generate random color palette
|
|
136
|
+
const hue1 = Math.random();
|
|
137
|
+
const hue2 = (hue1 + 0.1 + Math.random() * 0.15) % 1.0;
|
|
138
|
+
|
|
139
|
+
this.colors = {
|
|
140
|
+
blob1: this.hslToRgb(hue1, 0.9, 0.55),
|
|
141
|
+
blob2: this.hslToRgb(hue2, 0.85, 0.5),
|
|
142
|
+
bgTop: this.hslToRgb(hue1, 0.4, 0.05),
|
|
143
|
+
bgBottom: this.hslToRgb(hue2, 0.5, 0.12),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Pool blobs - wider pool at very bottom (partially off-screen)
|
|
147
|
+
this.poolBlobs = [
|
|
148
|
+
{ x: 0.2, y: 1.02, r: 0.06 },
|
|
149
|
+
{ x: 0.35, y: 1.0, r: 0.065 },
|
|
150
|
+
{ x: 0.5, y: 1.02, r: 0.07 },
|
|
151
|
+
{ x: 0.65, y: 1.0, r: 0.065 },
|
|
152
|
+
{ x: 0.8, y: 1.02, r: 0.06 },
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Rising/falling blobs - these move around
|
|
156
|
+
this.blobs = [];
|
|
157
|
+
|
|
158
|
+
// Start with one blob rising
|
|
159
|
+
this.blobs.push({
|
|
160
|
+
x: 0.5,
|
|
161
|
+
y: 0.6,
|
|
162
|
+
vx: 0,
|
|
163
|
+
vy: -0.0002,
|
|
164
|
+
r: 0.05,
|
|
165
|
+
targetR: 0.05,
|
|
166
|
+
temp: 0.8,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Metaball threshold
|
|
170
|
+
this.threshold = CONFIG.metaballThreshold;
|
|
171
|
+
this.time = 0;
|
|
172
|
+
this.lastSpawnTime = 0;
|
|
173
|
+
this.spawnInterval =
|
|
174
|
+
CONFIG.initialSpawnMin + Math.random() * CONFIG.initialSpawnRange;
|
|
175
|
+
|
|
176
|
+
// Lamp on/off state
|
|
177
|
+
this.lampOn = true;
|
|
178
|
+
this.heatLevel = 1.0; // Smooth transition 0-1
|
|
179
|
+
this.poolTemp = 1.0; // Pool temperature (cools when off)
|
|
180
|
+
|
|
181
|
+
// Create on/off toggle button
|
|
182
|
+
const btnWidth = 60;
|
|
183
|
+
const btnHeight = 36;
|
|
184
|
+
this.powerButton = new ToggleButton(this, {
|
|
185
|
+
text: "ON",
|
|
186
|
+
width: btnWidth,
|
|
187
|
+
height: btnHeight,
|
|
188
|
+
startToggled: true,
|
|
189
|
+
colorDefaultBg: "#000",
|
|
190
|
+
colorStroke: "#444",
|
|
191
|
+
colorText: "#888",
|
|
192
|
+
colorActiveBg: "#331100",
|
|
193
|
+
colorActiveStroke: "#ff6600",
|
|
194
|
+
colorActiveText: "#ff8833",
|
|
195
|
+
onToggle: (isOn) => {
|
|
196
|
+
this.lampOn = isOn;
|
|
197
|
+
this.powerButton.label.text = isOn ? "ON" : "OFF";
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
applyAnchor(this.powerButton, {
|
|
201
|
+
anchor: Position.CENTER_LEFT,
|
|
202
|
+
anchorOffsetX: 20,
|
|
203
|
+
});
|
|
204
|
+
this.pipeline.add(this.powerButton);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
shuffleColors() {
|
|
208
|
+
const hue1 = Math.random();
|
|
209
|
+
const hue2 = (hue1 + 0.1 + Math.random() * 0.15) % 1.0;
|
|
210
|
+
|
|
211
|
+
this.colors = {
|
|
212
|
+
blob1: this.hslToRgb(hue1, 0.9, 0.55),
|
|
213
|
+
blob2: this.hslToRgb(hue2, 0.85, 0.5),
|
|
214
|
+
bgTop: this.hslToRgb(hue1, 0.4, 0.05),
|
|
215
|
+
bgBottom: this.hslToRgb(hue2, 0.5, 0.12),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
hslToRgb(h, s, l) {
|
|
220
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
221
|
+
const x = c * (1 - Math.abs(((h * 6) % 2) - 1));
|
|
222
|
+
const m = l - c / 2;
|
|
223
|
+
let r, g, b;
|
|
224
|
+
|
|
225
|
+
if (h < 1 / 6) [r, g, b] = [c, x, 0];
|
|
226
|
+
else if (h < 2 / 6) [r, g, b] = [x, c, 0];
|
|
227
|
+
else if (h < 3 / 6) [r, g, b] = [0, c, x];
|
|
228
|
+
else if (h < 4 / 6) [r, g, b] = [0, x, c];
|
|
229
|
+
else if (h < 5 / 6) [r, g, b] = [x, 0, c];
|
|
230
|
+
else [r, g, b] = [c, 0, x];
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
Math.floor((r + m) * 255),
|
|
234
|
+
Math.floor((g + m) * 255),
|
|
235
|
+
Math.floor((b + m) * 255),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
update(dt) {
|
|
240
|
+
super.update(dt);
|
|
241
|
+
this.time += dt;
|
|
242
|
+
|
|
243
|
+
// Smooth heat level transition
|
|
244
|
+
const targetHeat = this.lampOn ? 1.0 : 0.0;
|
|
245
|
+
this.heatLevel += (targetHeat - this.heatLevel) * CONFIG.heatTransitionSpeed * dt * 60;
|
|
246
|
+
this.heatLevel = Math.max(0, Math.min(1, this.heatLevel));
|
|
247
|
+
|
|
248
|
+
// Pool temperature follows heat level (slower transition)
|
|
249
|
+
if (this.lampOn) {
|
|
250
|
+
this.poolTemp += (1.0 - this.poolTemp) * CONFIG.poolCoolRate * 2 * dt * 60;
|
|
251
|
+
} else {
|
|
252
|
+
this.poolTemp += (0.0 - this.poolTemp) * CONFIG.poolCoolRate * dt * 60;
|
|
253
|
+
}
|
|
254
|
+
this.poolTemp = Math.max(0, Math.min(1, this.poolTemp));
|
|
255
|
+
|
|
256
|
+
// Spawn new blobs from pool periodically (only when lamp is on and pool is hot)
|
|
257
|
+
// But only if no blobs are near the pool (to avoid immediate merging)
|
|
258
|
+
if (this.lampOn && this.poolTemp > 0.7 && this.time - this.lastSpawnTime > this.spawnInterval) {
|
|
259
|
+
const poolClear = !this.blobs.some(
|
|
260
|
+
(b) => b.y > CONFIG.poolClearThreshold && !b.growing,
|
|
261
|
+
);
|
|
262
|
+
if (poolClear) {
|
|
263
|
+
this.spawnBlob();
|
|
264
|
+
this.lastSpawnTime = this.time;
|
|
265
|
+
this.spawnInterval =
|
|
266
|
+
CONFIG.spawnMin + Math.random() * CONFIG.spawnRange;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.updateBlobs(dt);
|
|
271
|
+
this.renderLava();
|
|
272
|
+
|
|
273
|
+
// Update display
|
|
274
|
+
this.lavaImage.shape.bitmap = this.imageData;
|
|
275
|
+
this.lavaImage.x = 0;
|
|
276
|
+
this.lavaImage.y = 0;
|
|
277
|
+
this.lavaImage.width = this.width;
|
|
278
|
+
this.lavaImage.height = this.height;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
render() {
|
|
282
|
+
// Clear with background
|
|
283
|
+
this.ctx.fillStyle = this.backgroundColor;
|
|
284
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
285
|
+
|
|
286
|
+
// Draw lava with blend mode
|
|
287
|
+
this.ctx.globalCompositeOperation = CONFIG.blendMode;
|
|
288
|
+
this.pipeline.render();
|
|
289
|
+
this.ctx.globalCompositeOperation = "source-over";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
spawnBlob() {
|
|
293
|
+
// Release a blob from the pool - start deep inside the pool
|
|
294
|
+
const poolBlob =
|
|
295
|
+
this.poolBlobs[Math.floor(Math.random() * this.poolBlobs.length)];
|
|
296
|
+
this.blobs.push({
|
|
297
|
+
x: poolBlob.x + (Math.random() - 0.5) * CONFIG.spawnOffsetX,
|
|
298
|
+
y:
|
|
299
|
+
poolBlob.y +
|
|
300
|
+
CONFIG.spawnOffsetY +
|
|
301
|
+
Math.random() * CONFIG.spawnOffsetYRange,
|
|
302
|
+
vx: 0,
|
|
303
|
+
vy: 0,
|
|
304
|
+
r: CONFIG.initialRadius,
|
|
305
|
+
targetR:
|
|
306
|
+
CONFIG.targetRadiusMin + Math.random() * CONFIG.targetRadiusRange,
|
|
307
|
+
temp: 1.0,
|
|
308
|
+
growing: true,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Limit max blobs
|
|
312
|
+
if (this.blobs.length > CONFIG.maxBlobs) {
|
|
313
|
+
this.blobs.shift();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
updateBlobs(dt) {
|
|
318
|
+
// Thermal zone configuration - modified by lamp state
|
|
319
|
+
const thermalConfig = {
|
|
320
|
+
heatZone: CONFIG.heatZoneY,
|
|
321
|
+
coolZone: CONFIG.coolZoneY,
|
|
322
|
+
rate: CONFIG.tempRate,
|
|
323
|
+
transitionWidth: CONFIG.transitionWidth, // smooth zone boundaries
|
|
324
|
+
// When lamp is off, heat zone becomes weak/disabled, cool zone stronger
|
|
325
|
+
heatMultiplier: CONFIG.heatZoneMultiplier * this.heatLevel,
|
|
326
|
+
coolMultiplier: CONFIG.coolZoneMultiplier + (1 - this.heatLevel) * 2,
|
|
327
|
+
middleMultiplier: CONFIG.middleZoneMultiplier + (1 - this.heatLevel) * 0.3,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
for (const blob of this.blobs) {
|
|
331
|
+
// Temperature changes based on position (smooth zone transitions)
|
|
332
|
+
blob.temp = zoneTemperature(blob.y, blob.temp, thermalConfig);
|
|
333
|
+
|
|
334
|
+
// When lamp is off, all blobs cool down faster
|
|
335
|
+
if (!this.lampOn) {
|
|
336
|
+
blob.temp -= CONFIG.coolDownRate * dt * 60;
|
|
337
|
+
blob.temp = Math.max(0, blob.temp);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Buoyancy based on temperature
|
|
341
|
+
blob.vy -= thermalBuoyancy(blob.temp, 0.5, CONFIG.buoyancyStrength);
|
|
342
|
+
|
|
343
|
+
// Gravity pulls everything down - heavier blobs sink faster
|
|
344
|
+
blob.vy += thermalGravity(blob.r, CONFIG.targetRadiusMin, CONFIG.gravity);
|
|
345
|
+
|
|
346
|
+
// Gradually grow to target size (smooth spawn)
|
|
347
|
+
if (blob.targetR && blob.r < blob.targetR) {
|
|
348
|
+
blob.r += (blob.targetR - blob.r) * CONFIG.growthRate;
|
|
349
|
+
if (blob.growing && blob.r > blob.targetR * CONFIG.growthThreshold) {
|
|
350
|
+
blob.growing = false;
|
|
351
|
+
blob.vy = CONFIG.initialRiseVelocity;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Heat transfer and repulsion between nearby blobs
|
|
356
|
+
for (const other of this.blobs) {
|
|
357
|
+
if (other === blob) continue;
|
|
358
|
+
const dx = other.x - blob.x;
|
|
359
|
+
const dy = other.y - blob.y;
|
|
360
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
361
|
+
const combinedR = blob.r + other.r;
|
|
362
|
+
const maxDist = combinedR * CONFIG.heatTransferRange;
|
|
363
|
+
|
|
364
|
+
// Heat transfer
|
|
365
|
+
blob.temp += heatTransfer(blob.temp, other.temp, dist, maxDist, CONFIG.heatTransferRate);
|
|
366
|
+
|
|
367
|
+
// Repulsion force - push blobs apart horizontally to prevent clustering
|
|
368
|
+
const repulsionDist = combinedR * CONFIG.repulsionRange;
|
|
369
|
+
if (dist < repulsionDist && dist > 0.001) {
|
|
370
|
+
// Stronger repulsion when closer, falls off with distance
|
|
371
|
+
const repulsionFactor = 1 - (dist / repulsionDist);
|
|
372
|
+
const repulsion = repulsionFactor * repulsionFactor * CONFIG.repulsionStrength;
|
|
373
|
+
// Normalize direction and apply (mostly horizontal to spread out)
|
|
374
|
+
const invDist = 1 / dist;
|
|
375
|
+
blob.vx -= dx * invDist * repulsion * 1.5; // Stronger horizontal
|
|
376
|
+
blob.vy -= dy * invDist * repulsion * 0.3; // Weaker vertical
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
blob.temp = Math.max(0, Math.min(1, blob.temp));
|
|
380
|
+
|
|
381
|
+
// Chaos - random perturbation, stronger when hot (more fluid)
|
|
382
|
+
const chaosFactor = CONFIG.chaosStrength * (0.3 + blob.temp * CONFIG.chaosTempFactor);
|
|
383
|
+
blob.vx += (Math.random() - 0.5) * chaosFactor;
|
|
384
|
+
blob.vy += (Math.random() - 0.5) * chaosFactor * 0.5;
|
|
385
|
+
|
|
386
|
+
// Damping
|
|
387
|
+
blob.vx *= CONFIG.dampingX;
|
|
388
|
+
blob.vy *= CONFIG.dampingY;
|
|
389
|
+
|
|
390
|
+
// Organic horizontal drift using sine waves (unique per blob)
|
|
391
|
+
if (!blob.driftPhase) blob.driftPhase = Math.random() * Math.PI * 2;
|
|
392
|
+
if (!blob.driftSpeed)
|
|
393
|
+
blob.driftSpeed =
|
|
394
|
+
CONFIG.driftSpeedMin + Math.random() * CONFIG.driftSpeedRange;
|
|
395
|
+
if (!blob.driftAmount)
|
|
396
|
+
blob.driftAmount =
|
|
397
|
+
CONFIG.driftAmountMin + Math.random() * CONFIG.driftAmountRange;
|
|
398
|
+
|
|
399
|
+
const drift =
|
|
400
|
+
Math.sin(this.time * blob.driftSpeed + blob.driftPhase) *
|
|
401
|
+
blob.driftAmount;
|
|
402
|
+
blob.vx += drift;
|
|
403
|
+
|
|
404
|
+
// Subtle extra wobble when near top or bottom
|
|
405
|
+
if (blob.y < CONFIG.wobbleZoneTop || blob.y > CONFIG.wobbleZoneBottom) {
|
|
406
|
+
blob.vx += (Math.random() - 0.5) * CONFIG.wobbleForce;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Apply velocity
|
|
410
|
+
blob.x += blob.vx;
|
|
411
|
+
blob.y += blob.vy;
|
|
412
|
+
|
|
413
|
+
// Soft boundaries
|
|
414
|
+
if (blob.x < CONFIG.boundaryLeft) {
|
|
415
|
+
blob.x = CONFIG.boundaryLeft;
|
|
416
|
+
blob.vx *= CONFIG.boundaryBounce;
|
|
417
|
+
}
|
|
418
|
+
if (blob.x > CONFIG.boundaryRight) {
|
|
419
|
+
blob.x = CONFIG.boundaryRight;
|
|
420
|
+
blob.vx *= CONFIG.boundaryBounce;
|
|
421
|
+
}
|
|
422
|
+
if (blob.y < CONFIG.boundaryTop) {
|
|
423
|
+
blob.y = CONFIG.boundaryTop;
|
|
424
|
+
blob.vy *= CONFIG.boundaryBounce;
|
|
425
|
+
blob.temp = 0;
|
|
426
|
+
}
|
|
427
|
+
if (blob.y > CONFIG.removalY && blob.vy > 0 && !blob.growing) {
|
|
428
|
+
blob.removeMe = true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Speed limit
|
|
432
|
+
const speed = Math.sqrt(blob.vx * blob.vx + blob.vy * blob.vy);
|
|
433
|
+
if (speed > CONFIG.maxSpeed) {
|
|
434
|
+
blob.vx = (blob.vx / speed) * CONFIG.maxSpeed;
|
|
435
|
+
blob.vy = (blob.vy / speed) * CONFIG.maxSpeed;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Remove blobs that merged back into the pool
|
|
440
|
+
this.blobs = this.blobs.filter((b) => !b.removeMe);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
renderLava() {
|
|
444
|
+
const data = this.imageData.data;
|
|
445
|
+
const w = this.renderWidth;
|
|
446
|
+
const h = this.renderHeight;
|
|
447
|
+
const invW = 1 / w;
|
|
448
|
+
const invH = 1 / h;
|
|
449
|
+
|
|
450
|
+
// Desaturation factor when lamp is off (colors become duller)
|
|
451
|
+
const saturation = 0.4 + this.heatLevel * 0.6;
|
|
452
|
+
// Darkness multiplier - scene gets much darker when off
|
|
453
|
+
const darkness = 0.15 + this.heatLevel * 0.85;
|
|
454
|
+
|
|
455
|
+
// Pre-compute pool blob data (r² and weighted temp)
|
|
456
|
+
const poolTemp = this.poolTemp;
|
|
457
|
+
const poolData = this.poolBlobs.map(b => ({
|
|
458
|
+
x: b.x,
|
|
459
|
+
y: b.y,
|
|
460
|
+
rSq: b.r * b.r,
|
|
461
|
+
}));
|
|
462
|
+
|
|
463
|
+
// Pre-compute moving blob data once per frame (not per pixel!)
|
|
464
|
+
const blobData = this.blobs.map(blob => {
|
|
465
|
+
const phase = blob.driftPhase || 0;
|
|
466
|
+
const tempWobble = 0.3 + blob.temp * 0.7;
|
|
467
|
+
const wobble1 = Math.sin(this.time * CONFIG.wobble1Speed + phase) *
|
|
468
|
+
CONFIG.wobble1Amount * tempWobble;
|
|
469
|
+
const wobble2 = Math.sin(this.time * CONFIG.wobble2Speed + phase * CONFIG.wobble2PhaseFactor) *
|
|
470
|
+
CONFIG.wobble2Amount * tempWobble;
|
|
471
|
+
const stretch = CONFIG.stretchBase + blob.temp * CONFIG.stretchTempFactor + wobble1;
|
|
472
|
+
const skew = 1.0 + wobble2;
|
|
473
|
+
return {
|
|
474
|
+
x: blob.x,
|
|
475
|
+
y: blob.y,
|
|
476
|
+
rSq: blob.r * blob.r,
|
|
477
|
+
temp: blob.temp,
|
|
478
|
+
stretch,
|
|
479
|
+
skew,
|
|
480
|
+
invStretch: 1 / stretch,
|
|
481
|
+
stretchSkew: stretch * skew,
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Pre-compute color deltas and thresholds
|
|
486
|
+
const bgDeltaR = this.colors.bgBottom[0] - this.colors.bgTop[0];
|
|
487
|
+
const bgDeltaG = this.colors.bgBottom[1] - this.colors.bgTop[1];
|
|
488
|
+
const bgDeltaB = this.colors.bgBottom[2] - this.colors.bgTop[2];
|
|
489
|
+
const blob1 = this.colors.blob1;
|
|
490
|
+
const blob2 = this.colors.blob2;
|
|
491
|
+
const bgTop = this.colors.bgTop;
|
|
492
|
+
const innerThreshold = this.threshold;
|
|
493
|
+
const outerThreshold = innerThreshold - CONFIG.edgeWidth;
|
|
494
|
+
const invEdgeWidth = 1 / CONFIG.edgeWidth;
|
|
495
|
+
|
|
496
|
+
for (let py = 0; py < h; py++) {
|
|
497
|
+
const ny = py * invH;
|
|
498
|
+
|
|
499
|
+
// Background gradient with extra glow near pool at bottom
|
|
500
|
+
const poolGlow = ny > CONFIG.poolGlowStart
|
|
501
|
+
? (ny - CONFIG.poolGlowStart) / (1 - CONFIG.poolGlowStart)
|
|
502
|
+
: 0;
|
|
503
|
+
const glowBoost = poolGlow * poolGlow * CONFIG.poolGlowBoost * this.heatLevel;
|
|
504
|
+
|
|
505
|
+
// Background darkens when lamp is off
|
|
506
|
+
const bgR = (bgTop[0] + bgDeltaR * ny + blob1[0] * glowBoost) * darkness;
|
|
507
|
+
const bgG = (bgTop[1] + bgDeltaG * ny + blob1[1] * glowBoost * 0.6) * darkness;
|
|
508
|
+
const bgB = (bgTop[2] + bgDeltaB * ny + blob1[2] * glowBoost * 0.3) * darkness;
|
|
509
|
+
|
|
510
|
+
for (let px = 0; px < w; px++) {
|
|
511
|
+
const nx = px * invW;
|
|
512
|
+
const idx = (py * w + px) << 2; // Bitshift instead of * 4
|
|
513
|
+
|
|
514
|
+
// Calculate metaball field from pool + moving blobs
|
|
515
|
+
let sum = 0;
|
|
516
|
+
let avgTemp = 0;
|
|
517
|
+
|
|
518
|
+
// Pool blobs
|
|
519
|
+
for (let i = 0; i < poolData.length; i++) {
|
|
520
|
+
const p = poolData[i];
|
|
521
|
+
const dx = p.x - nx;
|
|
522
|
+
const dy = p.y - ny;
|
|
523
|
+
const distSq = dx * dx + dy * dy + 0.0001;
|
|
524
|
+
const influence = p.rSq / distSq;
|
|
525
|
+
sum += influence;
|
|
526
|
+
avgTemp += influence * poolTemp;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Moving blobs - using pre-computed stretch/skew
|
|
530
|
+
for (let i = 0; i < blobData.length; i++) {
|
|
531
|
+
const b = blobData[i];
|
|
532
|
+
const dx = b.x - nx;
|
|
533
|
+
const dy = b.y - ny;
|
|
534
|
+
const distSq = dx * dx * b.stretchSkew + dy * dy * b.invStretch + 0.0001;
|
|
535
|
+
const influence = b.rSq / distSq;
|
|
536
|
+
sum += influence;
|
|
537
|
+
avgTemp += influence * b.temp;
|
|
538
|
+
}
|
|
539
|
+
// Early exit for pure background pixels (biggest optimization)
|
|
540
|
+
if (sum < outerThreshold) {
|
|
541
|
+
data[idx] = bgR;
|
|
542
|
+
data[idx + 1] = bgG;
|
|
543
|
+
data[idx + 2] = bgB;
|
|
544
|
+
data[idx + 3] = 255;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (sum > 0) avgTemp /= sum;
|
|
549
|
+
|
|
550
|
+
// Blob color - only calculate when needed
|
|
551
|
+
const t = ny;
|
|
552
|
+
const tempInfluence = avgTemp * this.heatLevel;
|
|
553
|
+
const brightness =
|
|
554
|
+
(CONFIG.brightnessBase + t * CONFIG.brightnessTempFactor) *
|
|
555
|
+
(0.5 + tempInfluence * 0.5) * darkness;
|
|
556
|
+
|
|
557
|
+
const colorMix = t * saturation;
|
|
558
|
+
const invColorMix = 1 - colorMix;
|
|
559
|
+
const blobR = (blob1[0] * invColorMix + blob2[0] * colorMix) * brightness;
|
|
560
|
+
const blobG = (blob1[1] * invColorMix + blob2[1] * colorMix) * brightness;
|
|
561
|
+
const blobB = (blob1[2] * invColorMix + blob2[2] * colorMix) * brightness;
|
|
562
|
+
|
|
563
|
+
if (sum >= innerThreshold) {
|
|
564
|
+
// Fully inside blob
|
|
565
|
+
const glow = Math.min(
|
|
566
|
+
(sum - innerThreshold) * CONFIG.glowFactor * this.heatLevel,
|
|
567
|
+
CONFIG.glowMax * this.heatLevel,
|
|
568
|
+
);
|
|
569
|
+
data[idx] = Math.min(255, blobR + glow * CONFIG.glowR);
|
|
570
|
+
data[idx + 1] = Math.min(255, blobG + glow * CONFIG.glowG);
|
|
571
|
+
data[idx + 2] = Math.min(255, blobB + glow * CONFIG.glowB);
|
|
572
|
+
} else {
|
|
573
|
+
// Edge zone - blend between blob and background
|
|
574
|
+
const blend = (sum - outerThreshold) * invEdgeWidth;
|
|
575
|
+
const smoothBlend = blend * blend * (3 - 2 * blend);
|
|
576
|
+
data[idx] = bgR + (blobR - bgR) * smoothBlend;
|
|
577
|
+
data[idx + 1] = bgG + (blobG - bgG) * smoothBlend;
|
|
578
|
+
data[idx + 2] = bgB + (blobB - bgB) * smoothBlend;
|
|
579
|
+
}
|
|
580
|
+
data[idx + 3] = 255;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
window.addEventListener("load", () => {
|
|
587
|
+
const canvas = document.getElementById("game");
|
|
588
|
+
const demo = new LavaLampDemo(canvas);
|
|
589
|
+
demo.start();
|
|
590
|
+
});
|