@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.1
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/CLAUDE.md +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- package/package.json +62 -4
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
/* Copyright 2023-2026 by Essam Abadir */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* p5Provider — RendererProvider backed by p5.js 2.x (global mode).
|
|
5
|
+
*
|
|
6
|
+
* Implements the RendererProvider interface (renderer-provider-contract.js):
|
|
7
|
+
* init({ parentEl, width, height }) → HTMLCanvasElement (parentEl REQUIRED: requireMountHost)
|
|
8
|
+
* renderTick(tickData, largeObjects)
|
|
9
|
+
* filter(filterSpec) / removeFilter(name)
|
|
10
|
+
* setSize(width, height)
|
|
11
|
+
* start() / stop()
|
|
12
|
+
* destroy()
|
|
13
|
+
* raw()
|
|
14
|
+
* captureStill() — OPTIONAL (the `composite` stage tap; 2D-canvas readback)
|
|
15
|
+
*
|
|
16
|
+
* Design:
|
|
17
|
+
* - Operates in p5 global mode: p5 installs background/fill/etc. as globals (compat/vendor.js loads it).
|
|
18
|
+
* - FrqtlApp owns the RAF loop and calls renderTick() each draw frame.
|
|
19
|
+
* - The provider DOES NOT own the loop (start/stop are no-ops).
|
|
20
|
+
* - canvas creation happens in init() called from p5's setup() context.
|
|
21
|
+
* - filter() queues a post-process pass applied after each renderTick().
|
|
22
|
+
* - Post-process passes use WebGL2 on a secondary offscreen canvas and blit back.
|
|
23
|
+
*
|
|
24
|
+
* p5 2.x vs 0.6.0 compatibility:
|
|
25
|
+
* API surface: background, fill, noStroke, stroke, strokeWeight, noFill, ellipse,
|
|
26
|
+
* rect, createCanvas, loop, noLoop, frameRate — unchanged between 0.6.0 and 2.x.
|
|
27
|
+
* createCanvas() returns a p5.Element; .parent(element) still works.
|
|
28
|
+
* p5.dom is built into the main bundle in 2.x (no separate script tag needed).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import * as Camera2dGeometry from '../camera2d-geometry.js';
|
|
32
|
+
import * as CaptureStillCommon from '../captureStill-common.js';
|
|
33
|
+
import { FieldBuilder } from '../FieldBuilder.js';
|
|
34
|
+
import * as FieldDescriptorSchema from '../../../../shim/field-descriptor/index.js';
|
|
35
|
+
import { Filters } from '../Filters.js';
|
|
36
|
+
import * as FrameDataSchema from '../../../../shim/frame-data/index.js';
|
|
37
|
+
import { LightColor } from '../../../parts/LightColor.js';
|
|
38
|
+
import * as OrbitBag from '../orbit-bag.js';
|
|
39
|
+
import * as RenderCapabilitiesCommon from '../renderCapabilities-common.js';
|
|
40
|
+
import { RenderShape } from '../RenderShape.js';
|
|
41
|
+
import * as RendererProviderContract from '../renderer-provider-contract.js';
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
var VERT_SRC = [
|
|
45
|
+
'#version 300 es',
|
|
46
|
+
'in vec2 aPosition;',
|
|
47
|
+
'out vec2 vTexCoord;',
|
|
48
|
+
'void main() {',
|
|
49
|
+
' vTexCoord = aPosition * 0.5 + 0.5;',
|
|
50
|
+
' gl_Position = vec4(aPosition, 0.0, 1.0);',
|
|
51
|
+
'}'
|
|
52
|
+
].join('\n');
|
|
53
|
+
|
|
54
|
+
// ── Shader helpers ──────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
function compileShader(gl, type, src) {
|
|
57
|
+
var sh = gl.createShader(type);
|
|
58
|
+
gl.shaderSource(sh, src);
|
|
59
|
+
gl.compileShader(sh);
|
|
60
|
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
61
|
+
console.error('[p5-provider] shader compile error:', gl.getShaderInfoLog(sh));
|
|
62
|
+
gl.deleteShader(sh);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return sh;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function compileProgram(gl, vertSrc, fragSrc) {
|
|
69
|
+
var vs = compileShader(gl, gl.VERTEX_SHADER, vertSrc);
|
|
70
|
+
var fs = compileShader(gl, gl.FRAGMENT_SHADER, fragSrc);
|
|
71
|
+
if (!vs || !fs) return null;
|
|
72
|
+
var prog = gl.createProgram();
|
|
73
|
+
gl.attachShader(prog, vs);
|
|
74
|
+
gl.attachShader(prog, fs);
|
|
75
|
+
gl.linkProgram(prog);
|
|
76
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
|
|
77
|
+
console.error('[p5-provider] shader link error:', gl.getProgramInfoLog(prog));
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return prog;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Convert a GLSL 1.0-style fragment source to GLSL 300 es.
|
|
84
|
+
// Only used when the caller provides a glsl field without a version directive.
|
|
85
|
+
function upgradeGlsl(src) {
|
|
86
|
+
// Strip declarations we re-emit at the top (uniforms, varyings, precision)
|
|
87
|
+
var body = src
|
|
88
|
+
.replace(/precision\s+\w+\s+float\s*;/g, '')
|
|
89
|
+
.replace(/uniform\s+sampler2D\s+\w+\s*;/g, '')
|
|
90
|
+
.replace(/uniform\s+vec2\s+\w+\s*;/g, '')
|
|
91
|
+
.replace(/uniform\s+float\s+\w+\s*;/g, '')
|
|
92
|
+
.replace(/varying\s+vec2\s+\w+\s*;/g, '')
|
|
93
|
+
.replace(/texture2D\s*\(/g, 'texture(')
|
|
94
|
+
.replace(/gl_FragColor/g, 'fragColor');
|
|
95
|
+
return [
|
|
96
|
+
'#version 300 es',
|
|
97
|
+
'precision highp float;',
|
|
98
|
+
'out vec4 fragColor;',
|
|
99
|
+
'uniform sampler2D uField;',
|
|
100
|
+
'uniform sampler2D uScene;',
|
|
101
|
+
'uniform vec2 uResolution;',
|
|
102
|
+
'uniform float uTime;',
|
|
103
|
+
'in vec2 vTexCoord;',
|
|
104
|
+
body
|
|
105
|
+
].join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── renderCapabilities() (OPTIONAL contract method — S5a) ────────────────
|
|
109
|
+
//
|
|
110
|
+
// WS Studio Controls Realignment §7 r2 item 2/4: renderCapabilities() is an
|
|
111
|
+
// OPTIONAL RendererProvider method (the getCameraControl() precedent — absent
|
|
112
|
+
// ⇒ makeControlsForRenderSurface emits no render controls, never an error).
|
|
113
|
+
// Declares ONLY what THIS provider actually supports (architect condition
|
|
114
|
+
// "per-provider truth" — no schema-mirror registry claiming a capability a
|
|
115
|
+
// given backend can't honor). p5 supports the THREE universal knobs
|
|
116
|
+
// (shape/colorScheme/renderBaseCanvas — README §3: "both p5 and three
|
|
117
|
+
// providers honor this identically") plus every Filters.js entry (filter()/
|
|
118
|
+
// removeFilter() are REQUIRED contract methods on every provider) — it does
|
|
119
|
+
// NOT declare the three-only effect-layer knobs (godrays/glow/heat/smoke —
|
|
120
|
+
// p5 has no such render path; grep confirms).
|
|
121
|
+
//
|
|
122
|
+
// Defined as a MODULE-LEVEL free function (not inside makeP5Provider's
|
|
123
|
+
// per-instance closure) so it needs no live p5/WebGL context — capability
|
|
124
|
+
// DESCRIPTORS are static metadata (name/type/constraint/unit/default);
|
|
125
|
+
// live VALUES are read separately via FrqtlApp.getRenderOption /
|
|
126
|
+
// isFilterActive. This is what makes it unit-testable in plain Node.
|
|
127
|
+
// S5b DRY tidy (architect's S5a gate "minor DRY recommendation" — the 3
|
|
128
|
+
// universal descriptors + the filter loop were hand-copied between this
|
|
129
|
+
// file and three-provider.js): the ONE shared owner is now
|
|
130
|
+
// `renderCapabilities-common.js` — see that file's own docstring.
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
// THE 2D camera map (pan/zoom). Pure + backend-free, so the provider stays thin and the
|
|
134
|
+
// SAME map serves the offline unprojectStill — one authority, live and offline.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
// THE one field source (FieldBuilder.buildHeightField). An absent FieldBuilder is a load-order
|
|
138
|
+
// fault, never an all-zero field — the same rule capture.js's `field` tap throws on.
|
|
139
|
+
|
|
140
|
+
// THE point-shape authority for the pose bag (`{x,y,z}` AND `[x,y,z]`, one identical
|
|
141
|
+
// throw under every provider). This provider re-validates NO point field — a second
|
|
142
|
+
// opinion about a shape the contract owns is exactly the drift orbit-bag.js closes.
|
|
143
|
+
|
|
144
|
+
function _renderCapabilitiesP5() {
|
|
145
|
+
var normalizeFieldDescriptor = FieldDescriptorSchema.normalizeFieldDescriptor;
|
|
146
|
+
var common = RenderCapabilitiesCommon;
|
|
147
|
+
var FiltersRef = Filters;
|
|
148
|
+
return common.universalRenderCapabilities(
|
|
149
|
+
normalizeFieldDescriptor,
|
|
150
|
+
RenderShape,
|
|
151
|
+
FrameDataSchema
|
|
152
|
+
).concat(common.filterRenderCapabilities(normalizeFieldDescriptor, FiltersRef));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Factory ─────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
function makeP5Provider() {
|
|
158
|
+
// The runner's physics rect, stated by init({width, height}) / setSize(w, h). null until
|
|
159
|
+
// then: every read goes through _physW()/_physH(), which THROW before it is stated — a
|
|
160
|
+
// provider has no size of its own (RENDERER_CONTRACT §1 init row).
|
|
161
|
+
var _width = null;
|
|
162
|
+
var _height = null;
|
|
163
|
+
function _unsized() {
|
|
164
|
+
return new Error('p5Provider: the physics rect was read before init({width, height}) or ' +
|
|
165
|
+
'setSize(w, h) stated it. FrqtlApp.createCanvas hands the runner\'s canvas_rect to ' +
|
|
166
|
+
'provider.init — the provider has no default size.');
|
|
167
|
+
}
|
|
168
|
+
function _physW() { if (_width === null) { throw _unsized(); } return _width; }
|
|
169
|
+
function _physH() { if (_height === null) { throw _unsized(); } return _height; }
|
|
170
|
+
var _filters = []; // ordered list of active filterSpec objects
|
|
171
|
+
var _initialized = false;
|
|
172
|
+
var _p5Canvas = null; // p5.Element returned by createCanvas()
|
|
173
|
+
var _filterState = {}; // per-filter compiled WebGL2 state cache
|
|
174
|
+
var _lastTickData = null; // latest tickData for field-texture derivation
|
|
175
|
+
// The renderBaseCanvas value that the LAST composite actually ran with. Recorded
|
|
176
|
+
// at the point of compositing (not read from the runner later) because this
|
|
177
|
+
// provider is the authority for what it composited — a capture's provenance must
|
|
178
|
+
// state what produced the pixels, not what the option happens to say afterwards
|
|
179
|
+
// (RENDERER_CONTRACT §1a.4: no caller-supplied provenance). null until the first
|
|
180
|
+
// renderTick, which is exactly the "nothing composited yet" case.
|
|
181
|
+
var _lastRenderBaseCanvas = null;
|
|
182
|
+
// The `uTime` every filter pass of the LAST composite drew with (the contract's
|
|
183
|
+
// filterTime of the rendered tick), recorded where it is fed for the same reason;
|
|
184
|
+
// null when no filter ran.
|
|
185
|
+
var _lastFilterTime = null;
|
|
186
|
+
// The 2D camera pose — the DEGENERATE orbit bag {distance, target}. Initialised lazily
|
|
187
|
+
// (init() supplies the real dims). Null target = canvas centre; distance = visible
|
|
188
|
+
// physics-px height. This is CAMERA state, not a render option: every still stamps it
|
|
189
|
+
// as `pose`, so a zoomed 2D capture is self-describing instead of silently mislabeled.
|
|
190
|
+
var _pose = null;
|
|
191
|
+
function _poseOrDefault() {
|
|
192
|
+
if (!_pose) { _pose = Camera2dGeometry.defaultPose2d(_physW(), _physH()); }
|
|
193
|
+
return _pose;
|
|
194
|
+
}
|
|
195
|
+
// The whole 2D pose bag, COPIED — never the live object (a captured pose must not keep
|
|
196
|
+
// tracking the camera; the still-provenance aliasing discipline): what getOrbit()
|
|
197
|
+
// returns and what every pose-change listener receives.
|
|
198
|
+
function _poseBag() {
|
|
199
|
+
var p = _poseOrDefault();
|
|
200
|
+
return { distance: p.distance, target: p.target ? { x: p.target.x, y: p.target.y } : null };
|
|
201
|
+
}
|
|
202
|
+
// The pose-change listener list (OrbitBag.makePoseNotice), provider-scoped so every
|
|
203
|
+
// handle getCameraControl() returns shares it. Created on the first subscription; a
|
|
204
|
+
// pose write (setOrbit, setPreset, a resize) notifies it.
|
|
205
|
+
var _poseNotice = null;
|
|
206
|
+
function _notifyPose() { if (_poseNotice) { _poseNotice.notify(_poseBag()); } }
|
|
207
|
+
|
|
208
|
+
// ── Layer helpers ───────────────────────────────────────────────────
|
|
209
|
+
//
|
|
210
|
+
// Layers are a RENDERER-SIDE organizing pattern over the per-tick handle
|
|
211
|
+
// (tickData.frameData + largeObjects). There is NO engine concept of a
|
|
212
|
+
// layer and NO spec-side layer descriptor — the structure is intrinsic to
|
|
213
|
+
// the handle: each quantum already self-identifies its dimension via
|
|
214
|
+
// FrameData.layer, and largeObjects carry their own render attributes
|
|
215
|
+
// (visible/color/alpha/drawAsCircle/backdrop/image). Each helper below
|
|
216
|
+
// walks the handle and draws one named layer. The IDE Layers panel reflects
|
|
217
|
+
// these by reading dimensionData ∪ largeObjects and toggles them via the
|
|
218
|
+
// per-object .visible flag / renderBaseCanvas — no new contract.
|
|
219
|
+
//
|
|
220
|
+
// p5 draws the layers here; three/custom implement the SAME named layers in
|
|
221
|
+
// their own renderTick. Swapping renderers never touches this file — only
|
|
222
|
+
// the RendererProvider interface + the handle are shared.
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Backdrop layer: non-colliding LargeObjects carrying an image, drawn first
|
|
226
|
+
* (under everything). The image is a p5.Image / HTMLImageElement loaded by
|
|
227
|
+
* the experiment page; the renderer only composites it into the LO's rect.
|
|
228
|
+
*/
|
|
229
|
+
function _layerBackdrop(largeObjects) {
|
|
230
|
+
if (!largeObjects || typeof image !== 'function') return;
|
|
231
|
+
for (var i = 0; i < largeObjects.length; i++) {
|
|
232
|
+
var lo = largeObjects[i];
|
|
233
|
+
if (lo.visible === false || !lo.backdrop || !lo.image) continue;
|
|
234
|
+
image(lo.image, lo.rect.left, lo.rect.top, lo.rect.w, lo.rect.h);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Bodies layer: structural LargeObjects (walls, detectors, blocks) — the
|
|
240
|
+
* colliding sprites. Backdrops are skipped here (drawn in _layerBackdrop).
|
|
241
|
+
*/
|
|
242
|
+
function _layerBodies(largeObjects) {
|
|
243
|
+
if (!largeObjects) return;
|
|
244
|
+
for (var i = 0; i < largeObjects.length; i++) {
|
|
245
|
+
var lo = largeObjects[i];
|
|
246
|
+
if (lo.visible === false || lo.backdrop) continue;
|
|
247
|
+
var loa = (lo.alpha !== undefined) ? lo.alpha : 255;
|
|
248
|
+
// LiveChart: composite the ferried OFFSCREEN Chart.js canvas as an in-scene
|
|
249
|
+
// image panel (the R5 ferry contract). The renderer only wraps the canvas
|
|
250
|
+
// element — Chart.js never enters the renderer. lo.liveCanvas is an accessor
|
|
251
|
+
// returning the CURRENT element (it churns each redraw). drawingContext.drawImage
|
|
252
|
+
// is the safe cross-version path (p5's image() may reject a raw canvas); a colored
|
|
253
|
+
// panel shell draws until the first chart render lands.
|
|
254
|
+
if (lo.liveCanvas) {
|
|
255
|
+
var _lcv = (typeof lo.liveCanvas === 'function') ? lo.liveCanvas() : lo.liveCanvas;
|
|
256
|
+
var _drewLive = false;
|
|
257
|
+
push();
|
|
258
|
+
if (_lcv && typeof drawingContext !== 'undefined' && drawingContext &&
|
|
259
|
+
typeof drawingContext.drawImage === 'function') {
|
|
260
|
+
var _pa = drawingContext.globalAlpha;
|
|
261
|
+
drawingContext.globalAlpha = Math.max(0, Math.min(1, loa / 255));
|
|
262
|
+
try {
|
|
263
|
+
drawingContext.drawImage(_lcv, lo.rect.left, lo.rect.top, lo.rect.w, lo.rect.h);
|
|
264
|
+
_drewLive = true;
|
|
265
|
+
} catch (e) { /* pre-layout frame — fall through to the panel shell */ }
|
|
266
|
+
drawingContext.globalAlpha = _pa;
|
|
267
|
+
} else if (_lcv && typeof image === 'function') {
|
|
268
|
+
try { image(_lcv, lo.rect.left, lo.rect.top, lo.rect.w, lo.rect.h); _drewLive = true; }
|
|
269
|
+
catch (e) { /* fall through */ }
|
|
270
|
+
}
|
|
271
|
+
if (!_drewLive) {
|
|
272
|
+
noStroke();
|
|
273
|
+
fill(lo.color[0], lo.color[1], lo.color[2], loa);
|
|
274
|
+
rect(lo.rect.left, lo.rect.top, lo.rect.w, lo.rect.h);
|
|
275
|
+
}
|
|
276
|
+
pop();
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
// Shell SHAPE (Bite 5b R2-3, capability-based, zero engine change): a per-LO
|
|
280
|
+
// shellShape (ferried render.shape vocab) picks the drawn primitive — box/cube →
|
|
281
|
+
// rect, ball/circle/sphere → ellipse (cube/sphere degrade to box/ball in 2D).
|
|
282
|
+
// Absent → the legacy drawAsCircle boolean (byte-identical for every prior LO).
|
|
283
|
+
var _asCircle;
|
|
284
|
+
if (typeof lo.shellShape === 'string' && lo.shellShape) {
|
|
285
|
+
_asCircle = (lo.shellShape !== 'box' && lo.shellShape !== 'cube');
|
|
286
|
+
} else {
|
|
287
|
+
_asCircle = !!lo.drawAsCircle;
|
|
288
|
+
}
|
|
289
|
+
var _cx = lo.rect.left + lo.rect.w / 2, _cy = lo.rect.top + lo.rect.h / 2;
|
|
290
|
+
// R2 — VISIBLE ROTATION: a ferried orientationDeg (Wall sugar) rotates the drawn
|
|
291
|
+
// shell about its center. The drawn face is the wall: a rotated wall draws NO
|
|
292
|
+
// outline of its un-rotated engine rect. orientation 0 ⇒ the legacy axis-aligned
|
|
293
|
+
// draw, byte-identical (SU1). NOTE: lo.rect is the wrapper's TRUE-slab rect (the
|
|
294
|
+
// broad-phase-superset expansion applies only to the ENGINE's registered rect).
|
|
295
|
+
var _deg = (typeof lo.orientationDeg === 'number') ? (((lo.orientationDeg % 360) + 360) % 360) : 0;
|
|
296
|
+
if (_deg !== 0) {
|
|
297
|
+
// the rotated shell face (drawn centered under the rotation transform).
|
|
298
|
+
push();
|
|
299
|
+
noStroke();
|
|
300
|
+
fill(lo.color[0], lo.color[1], lo.color[2], loa);
|
|
301
|
+
translate(_cx, _cy);
|
|
302
|
+
rotate(radians(_deg));
|
|
303
|
+
if (_asCircle) { ellipse(0, 0, lo.rect.w, lo.rect.h); }
|
|
304
|
+
else { rect(-lo.rect.w / 2, -lo.rect.h / 2, lo.rect.w, lo.rect.h); }
|
|
305
|
+
pop();
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
noStroke();
|
|
309
|
+
fill(lo.color[0], lo.color[1], lo.color[2], loa);
|
|
310
|
+
if (_asCircle) {
|
|
311
|
+
ellipse(_cx, _cy, lo.rect.w, lo.rect.h);
|
|
312
|
+
} else {
|
|
313
|
+
rect(lo.rect.left, lo.rect.top, lo.rect.w, lo.rect.h);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Particle layers: leaf-frame quanta grouped by their self-organizing
|
|
320
|
+
* .layer (the dimension number the engine assigns — FrameData.layer),
|
|
321
|
+
* sorted ascending so higher dimensions composite on top. Order WITHIN a
|
|
322
|
+
* layer is preserved, so single-dimension experiments (wave-interference)
|
|
323
|
+
* render byte-identically to the pre-refactor frame loop.
|
|
324
|
+
*
|
|
325
|
+
* @param {object} tickData
|
|
326
|
+
* @param {object} renderOpts — the runner's _renderOptions bag (setRenderOption).
|
|
327
|
+
*/
|
|
328
|
+
function _layerParticles(tickData, renderOpts) {
|
|
329
|
+
if (!tickData || !tickData.frameData) return;
|
|
330
|
+
// LightColor + RenderShape are MANDATORY canonical contracts (the single color/shape
|
|
331
|
+
// sources). A missing one is a hard load-order/packaging error — FAIL LOUD, never a
|
|
332
|
+
// silent fallback to white/'ball' (Invariant 2; architect ruling 2026-06-30).
|
|
333
|
+
var lc = LightColor;
|
|
334
|
+
var rs = RenderShape;
|
|
335
|
+
|
|
336
|
+
var byLayer = {};
|
|
337
|
+
var order = [];
|
|
338
|
+
for (var j = 0; j < tickData.frameData.length; j++) {
|
|
339
|
+
var L = tickData.frameData[j].layer;
|
|
340
|
+
var key = (L === undefined || L === null) ? 0 : L;
|
|
341
|
+
if (!byLayer[key]) { byLayer[key] = []; order.push(key); }
|
|
342
|
+
byLayer[key].push(tickData.frameData[j]);
|
|
343
|
+
}
|
|
344
|
+
order.sort(function (a, b) { return a - b; });
|
|
345
|
+
|
|
346
|
+
for (var oi = 0; oi < order.length; oi++) {
|
|
347
|
+
var group = byLayer[order[oi]];
|
|
348
|
+
for (var k = 0; k < group.length; k++) { _drawQuantum(group[k], lc, rs, renderOpts); }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Draw a single quantum.
|
|
354
|
+
*
|
|
355
|
+
* Color priority:
|
|
356
|
+
* render.color (exact CSV r,g,b,a)
|
|
357
|
+
* > render.colorScheme per-frame key in string_map (LightColor ramp)
|
|
358
|
+
* > renderOpts.colorScheme default (LightColor ramp)
|
|
359
|
+
* > 'warmcool' default
|
|
360
|
+
*
|
|
361
|
+
* Shape priority: RenderShape.resolve(frame, renderOpts) — single authority.
|
|
362
|
+
* 'ball' → round glow ellipse + halo (existing default behavior)
|
|
363
|
+
* 'box' → solid rect at voxel size
|
|
364
|
+
* 'circle' → outline ellipse (noFill + stroke)
|
|
365
|
+
*/
|
|
366
|
+
function _drawQuantum(fd, lc, rs, renderOpts) {
|
|
367
|
+
var rgb;
|
|
368
|
+
// Render color: user-space data on the generic string_map (CSV 'r,g,b,a'); the engine
|
|
369
|
+
// carries it opaque (I1). Same channel as render.outline.
|
|
370
|
+
var _sm = fd.data && fd.data.string_map;
|
|
371
|
+
var renderColor = null;
|
|
372
|
+
if (_sm && _sm['render.color']) {
|
|
373
|
+
var _cp = String(_sm['render.color']).split(',').map(Number);
|
|
374
|
+
if (_cp.length >= 3 && _cp.every(function (n) { return !isNaN(n); })) renderColor = _cp;
|
|
375
|
+
}
|
|
376
|
+
// Color scheme: per-frame override > experiment default > 'warmcool'.
|
|
377
|
+
var frameScheme = (_sm && _sm['render.colorScheme']) ? _sm['render.colorScheme']
|
|
378
|
+
: ((renderOpts && renderOpts.colorScheme) ? renderOpts.colorScheme : 'warmcool');
|
|
379
|
+
|
|
380
|
+
if (renderColor) {
|
|
381
|
+
rgb = renderColor;
|
|
382
|
+
} else {
|
|
383
|
+
// LightColor is the single color authority — getRgbBySchemeName dispatches by scheme
|
|
384
|
+
// and always returns [r,g,b]. Alpha: single authority — getAlphaFromIntensity (wider
|
|
385
|
+
// dynamic range; eliminates the old 180/255 floor so sparse quanta read visually dim).
|
|
386
|
+
var color = lc.getRgbBySchemeName(frameScheme, fd.fullness);
|
|
387
|
+
var alpha01 = lc.getAlphaFromIntensity(fd.fullness);
|
|
388
|
+
rgb = [color[0], color[1], color[2], alpha01];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Shape: RenderShape.resolve is the single authority (it returns 'ball' as its own default).
|
|
392
|
+
var shape = rs.resolve(fd, renderOpts);
|
|
393
|
+
var r = rgb[0], g = rgb[1], b = rgb[2], a = (rgb[3] !== undefined ? rgb[3] : 1);
|
|
394
|
+
|
|
395
|
+
noStroke();
|
|
396
|
+
if (shape === 'box') {
|
|
397
|
+
// Solid filled rectangle at the voxel size. Gives boxes-of-balls a crisp voxel look.
|
|
398
|
+
fill(r, g, b, a * 255);
|
|
399
|
+
rect(fd.rect.left, fd.rect.top, fd.rect.w, fd.rect.h);
|
|
400
|
+
} else if (shape === 'circle') {
|
|
401
|
+
// Outline circle — noFill + stroke, good for wave-front / boundary visualization.
|
|
402
|
+
strokeWeight(1);
|
|
403
|
+
stroke(r, g, b, a * 255);
|
|
404
|
+
noFill();
|
|
405
|
+
ellipse(fd.rect.x, fd.rect.y, fd.rect.w, fd.rect.h);
|
|
406
|
+
} else {
|
|
407
|
+
// 'ball' (default) — round glow ellipse + halo (existing behavior, unchanged).
|
|
408
|
+
// render.outline is a legacy override for this branch: 'true' → rect outline.
|
|
409
|
+
var _outline = !!(_sm && _sm['render.outline'] === 'true');
|
|
410
|
+
if (!_outline) {
|
|
411
|
+
var r1 = Math.max(1, fd.rect.w / 2);
|
|
412
|
+
var r2 = Math.max(2, fd.rect.w);
|
|
413
|
+
fill(r, g, b, a * 255);
|
|
414
|
+
ellipse(fd.rect.x, fd.rect.y, r1, r1);
|
|
415
|
+
fill(r, g, b, 25);
|
|
416
|
+
ellipse(fd.rect.x, fd.rect.y, r2, r2);
|
|
417
|
+
} else {
|
|
418
|
+
strokeWeight(1);
|
|
419
|
+
stroke(r, g, b, a * 255);
|
|
420
|
+
noFill();
|
|
421
|
+
rect(fd.rect.left, fd.rect.top, fd.rect.w, fd.rect.h);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── Compose the named layers in z-order ─────────────────────────────
|
|
427
|
+
// Per-layer visibility is INDEPENDENT:
|
|
428
|
+
// backdrop & bodies — toggle per-object via LargeObject.visible
|
|
429
|
+
// particles — toggle via drawParticles (renderBaseCanvas gate)
|
|
430
|
+
// Hiding particles still composes backdrop + bodies, so e.g. a pond
|
|
431
|
+
// backdrop refracts under the water shader without the photons. This is
|
|
432
|
+
// the per-layer independence the IDE Layers panel rides on.
|
|
433
|
+
function _composeLayers(tickData, largeObjects, drawParticles, renderOpts) {
|
|
434
|
+
if (typeof background === 'undefined') return; // p5 not in global scope yet
|
|
435
|
+
background(0);
|
|
436
|
+
// ── 2D CAMERA: pan/zoom applied ONCE, around every layer ──
|
|
437
|
+
// Derived from the pose through the one authority, so the picture and
|
|
438
|
+
// screenToWorld/unprojectStill can never disagree. At the default pose k === 1
|
|
439
|
+
// and this is the identity transform — every pre-existing scene is byte-unchanged.
|
|
440
|
+
var v = Camera2dGeometry.resolveView2d(_poseOrDefault(), _physW(), _physH());
|
|
441
|
+
var _framed = (typeof push === 'function' && typeof scale === 'function' &&
|
|
442
|
+
typeof translate === 'function');
|
|
443
|
+
if (_framed) {
|
|
444
|
+
push();
|
|
445
|
+
translate(_physW() / 2, _physH() / 2);
|
|
446
|
+
scale(v.k);
|
|
447
|
+
translate(-v.cx, -v.cy);
|
|
448
|
+
}
|
|
449
|
+
_layerBackdrop(largeObjects); // non-colliding image LOs (under all)
|
|
450
|
+
_layerBodies(largeObjects); // structural sprites
|
|
451
|
+
if (drawParticles) { _layerParticles(tickData, renderOpts); } // quanta, grouped by .layer
|
|
452
|
+
if (_framed) { pop(); }
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ── Filter post-processing (WebGL2) ─────────────────────────────────
|
|
456
|
+
|
|
457
|
+
// The provider's OWN canvas — the element p5's createCanvas() returned in init(). Never
|
|
458
|
+
// looked up in the document: the first <canvas> on a page is not necessarily this one.
|
|
459
|
+
function _getP5Canvas() {
|
|
460
|
+
if (_p5Canvas && _p5Canvas.elt) return _p5Canvas.elt;
|
|
461
|
+
throw new Error('p5Provider: no canvas to read — the provider\'s p5 canvas element is absent. ' +
|
|
462
|
+
'init({width, height}) creates it (p5\'s createCanvas() → .elt); call init first ' +
|
|
463
|
+
'(FrqtlApp.createCanvas does).');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Smooth heightfield via shared FieldBuilder (single source, identical to three provider).
|
|
467
|
+
function _buildFieldTexture() {
|
|
468
|
+
if (!_lastTickData || !_lastTickData.frameData) {
|
|
469
|
+
return new Uint8Array(FieldBuilder.FIELD_OPTS.w * FieldBuilder.FIELD_OPTS.h);
|
|
470
|
+
}
|
|
471
|
+
// FieldBuilder uses its own fixed resolution (FIELD_OPTS.w x h), not canvas size;
|
|
472
|
+
// canvasW/canvasH map physics px onto it.
|
|
473
|
+
return FieldBuilder.buildHeightField(_lastTickData.frameData,
|
|
474
|
+
Object.assign({}, FieldBuilder.FIELD_OPTS, { canvasW: _physW(), canvasH: _physH() }));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function _initFilterState(spec, canvas) {
|
|
478
|
+
var offscreen = document.createElement('canvas');
|
|
479
|
+
offscreen.width = canvas.width;
|
|
480
|
+
offscreen.height = canvas.height;
|
|
481
|
+
var gl = offscreen.getContext('webgl2');
|
|
482
|
+
if (!gl) {
|
|
483
|
+
console.warn('[p5-provider] WebGL2 unavailable for filter:', spec.name);
|
|
484
|
+
return { skip: true };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
var fragSrc = (spec.glsl.indexOf('#version') === -1)
|
|
488
|
+
? upgradeGlsl(spec.glsl)
|
|
489
|
+
: spec.glsl;
|
|
490
|
+
|
|
491
|
+
var prog = compileProgram(gl, VERT_SRC, fragSrc);
|
|
492
|
+
if (!prog) return { skip: true };
|
|
493
|
+
|
|
494
|
+
var quadBuf = gl.createBuffer();
|
|
495
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
|
496
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
|
|
497
|
+
|
|
498
|
+
function makeTex() {
|
|
499
|
+
var t = gl.createTexture();
|
|
500
|
+
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
501
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
502
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
503
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
504
|
+
return t;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
gl: gl, prog: prog, quadBuf: quadBuf,
|
|
509
|
+
sceneTex: makeTex(), fieldTex: makeTex(),
|
|
510
|
+
offscreen: offscreen
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function _runFilter(spec, time) {
|
|
515
|
+
var canvas = _getP5Canvas();
|
|
516
|
+
|
|
517
|
+
var key = spec.name || 'filter';
|
|
518
|
+
var state = _filterState[key];
|
|
519
|
+
if (!state) {
|
|
520
|
+
state = _initFilterState(spec, canvas);
|
|
521
|
+
_filterState[key] = state;
|
|
522
|
+
}
|
|
523
|
+
if (state.skip) return;
|
|
524
|
+
|
|
525
|
+
var gl = state.gl;
|
|
526
|
+
// R-7: pixelDensity fix — the p5 canvas's LOGICAL size vs. its
|
|
527
|
+
// backing-store size may differ by the device pixel ratio (pixelDensity).
|
|
528
|
+
// Two sizes matter here:
|
|
529
|
+
// logW / logH = LOGICAL pixels = the physics canvas size, the SOT
|
|
530
|
+
// (init's opts.width/height ← runner.canvas_rect; ==
|
|
531
|
+
// createCanvas(_width,_height)). The p5 2D context is
|
|
532
|
+
// scaled by pixelDensity, so it draws in THESE units and
|
|
533
|
+
// a full-canvas rect spans (0,0)→(logW,logH).
|
|
534
|
+
// bsW / bsH = backing-store pixels (canvas.width / canvas.height =
|
|
535
|
+
// logical × pixelDensity). The offscreen WebGL2 buffer,
|
|
536
|
+
// viewport, and uResolution use the backing store.
|
|
537
|
+
//
|
|
538
|
+
// BUGFIX (letterbox interaction): the blit dst rect below MUST be the
|
|
539
|
+
// LOGICAL/SOT size (logW/logH), NOT canvas.clientWidth/clientHeight.
|
|
540
|
+
// clientWidth/Height is the DISPLAYED element box, which the object-fit
|
|
541
|
+
// letterbox (+ max-width/max-height:100% clamps in experiment-layout.css)
|
|
542
|
+
// distorts to a wide/short rectangle on a wide viewport. Blitting into
|
|
543
|
+
// that distorted box left the bottom of the square uncovered (raw
|
|
544
|
+
// particles showing through under the water shader). logW/logH is the
|
|
545
|
+
// same SOT the base particle draw + the object-fit letterbox consume, so
|
|
546
|
+
// the shader now covers the FULL physics square at any viewport width.
|
|
547
|
+
var bsW = canvas.width;
|
|
548
|
+
var bsH = canvas.height;
|
|
549
|
+
var logW = _physW();
|
|
550
|
+
var logH = _physH();
|
|
551
|
+
|
|
552
|
+
// Resize the offscreen buffer only when the backing-store size changes.
|
|
553
|
+
if (state.offscreen.width !== bsW || state.offscreen.height !== bsH) {
|
|
554
|
+
state.offscreen.width = bsW;
|
|
555
|
+
state.offscreen.height = bsH;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
gl.viewport(0, 0, bsW, bsH);
|
|
559
|
+
|
|
560
|
+
// Upload scene (from the backing store — full bsW × bsH)
|
|
561
|
+
gl.bindTexture(gl.TEXTURE_2D, state.sceneTex);
|
|
562
|
+
try { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); }
|
|
563
|
+
catch (e) { return; }
|
|
564
|
+
|
|
565
|
+
// Upload field (smooth heightfield from shared FieldBuilder; fixed resolution)
|
|
566
|
+
var fOpts = FieldBuilder.FIELD_OPTS;
|
|
567
|
+
var fw = fOpts.w;
|
|
568
|
+
var fh = fOpts.h;
|
|
569
|
+
var fieldData = _buildFieldTexture();
|
|
570
|
+
gl.bindTexture(gl.TEXTURE_2D, state.fieldTex);
|
|
571
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, fw, fh, 0, gl.RED, gl.UNSIGNED_BYTE, fieldData);
|
|
572
|
+
|
|
573
|
+
// Draw
|
|
574
|
+
gl.useProgram(state.prog);
|
|
575
|
+
var posLoc = gl.getAttribLocation(state.prog, 'aPosition');
|
|
576
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, state.quadBuf);
|
|
577
|
+
gl.enableVertexAttribArray(posLoc);
|
|
578
|
+
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
579
|
+
|
|
580
|
+
// uResolution = backing-store size so texel = 1/uResolution matches the texture.
|
|
581
|
+
// The finite-difference normal uses this to step one texel in UV space.
|
|
582
|
+
gl.uniform2f(gl.getUniformLocation(state.prog, 'uResolution'), bsW, bsH);
|
|
583
|
+
// uTime = simulation seconds at the rendered tick (the contract's filterTime).
|
|
584
|
+
gl.uniform1f(gl.getUniformLocation(state.prog, 'uTime'), time);
|
|
585
|
+
|
|
586
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
587
|
+
gl.bindTexture(gl.TEXTURE_2D, state.sceneTex);
|
|
588
|
+
gl.uniform1i(gl.getUniformLocation(state.prog, 'uScene'), 0);
|
|
589
|
+
|
|
590
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
591
|
+
gl.bindTexture(gl.TEXTURE_2D, state.fieldTex);
|
|
592
|
+
gl.uniform1i(gl.getUniformLocation(state.prog, 'uField'), 1);
|
|
593
|
+
|
|
594
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
595
|
+
|
|
596
|
+
// R-7 FIX: Blit result back to p5 canvas via 2D context with explicit
|
|
597
|
+
// src (backing-store) → dst (LOGICAL/SOT) rect mapping.
|
|
598
|
+
//
|
|
599
|
+
// The p5 2D context is scaled by pixelDensity, so it draws in LOGICAL
|
|
600
|
+
// units: a full-canvas rect is (0,0)→(logW,logH). The offscreen buffer
|
|
601
|
+
// is bsW × bsH (= logical × pixelDensity). Specifying the full
|
|
602
|
+
// src (0,0,bsW,bsH) → full dst (0,0,logW,logH) stretches the entire
|
|
603
|
+
// shader output over the entire physics canvas, regardless of
|
|
604
|
+
// pixelDensity AND regardless of how CSS (object-fit letterbox) then
|
|
605
|
+
// scales the element for display. Using the DISPLAYED client size here
|
|
606
|
+
// instead would couple the framebuffer to the letterboxed box and leave
|
|
607
|
+
// part of the square uncovered on a wide viewport (the bug this fixes).
|
|
608
|
+
var ctx2d = canvas.getContext('2d');
|
|
609
|
+
if (ctx2d) {
|
|
610
|
+
ctx2d.drawImage(state.offscreen,
|
|
611
|
+
0, 0, bsW, bsH, // source: full offscreen (backing-store pixels)
|
|
612
|
+
0, 0, logW, logH // destination: full physics canvas (logical/SOT units)
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ── Public interface ─────────────────────────────────────────────────
|
|
618
|
+
|
|
619
|
+
var provider = {
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* init — create the p5 canvas and RETURN it as an HTMLCanvasElement (`.elt`).
|
|
623
|
+
* MUST be called from inside p5's window.setup() (createCanvas is a p5 global).
|
|
624
|
+
*/
|
|
625
|
+
init: function (opts) {
|
|
626
|
+
RendererProviderContract.requireProviderSize('p5Provider.init',
|
|
627
|
+
opts && opts.width, opts && opts.height);
|
|
628
|
+
_width = opts.width;
|
|
629
|
+
_height = opts.height;
|
|
630
|
+
// The mount host is resolved BEFORE createCanvas: a missing or unknown canvasParentId
|
|
631
|
+
// throws the contract's sentence instead of leaving the canvas at p5's own body default
|
|
632
|
+
// (null) or reaching p5's parent(id), whose missing-id path is a bare TypeError.
|
|
633
|
+
var host = RendererProviderContract.requireMountHost('p5Provider.init', opts.parentEl);
|
|
634
|
+
_p5Canvas = createCanvas(_physW(), _physH());
|
|
635
|
+
_p5Canvas.parent(host);
|
|
636
|
+
_initialized = true;
|
|
637
|
+
return _getP5Canvas(); // the HTMLCanvasElement (RENDERER_CONTRACT §1 init row)
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
renderTick: function (tickData, largeObjects, options) {
|
|
641
|
+
_lastTickData = tickData;
|
|
642
|
+
// renderBaseCanvas gates ONLY the particle layer (back-compat: the
|
|
643
|
+
// "shade particles" toggle). The backdrop + bodies layers always
|
|
644
|
+
// compose, so hiding particles leaves other layers independently
|
|
645
|
+
// visible (e.g. a pond backdrop refracting under water without the
|
|
646
|
+
// photons). Default true = particles drawn. Never touches uField/physics.
|
|
647
|
+
var drawParticles = !options || options.renderBaseCanvas !== false;
|
|
648
|
+
_lastRenderBaseCanvas = drawParticles; // capture provenance (§1a.4)
|
|
649
|
+
_composeLayers(tickData, largeObjects, drawParticles, options);
|
|
650
|
+
var time = _filters.length > 0
|
|
651
|
+
? RendererProviderContract.filterTime(tickData) : null;
|
|
652
|
+
_lastFilterTime = time; // capture provenance (§1a.4)
|
|
653
|
+
for (var i = 0; i < _filters.length; i++) { _runFilter(_filters[i], time); }
|
|
654
|
+
},
|
|
655
|
+
|
|
656
|
+
filter: function (filterSpec, perFilterOptions) {
|
|
657
|
+
// R-6: perFilterOptions (optional) merges over the runner options for this
|
|
658
|
+
// specific filter call. The merged options are stored on the spec copy so
|
|
659
|
+
// renderTick picks them up on the next draw.
|
|
660
|
+
if (!filterSpec) return;
|
|
661
|
+
if (filterSpec.name) {
|
|
662
|
+
for (var i = 0; i < _filters.length; i++) {
|
|
663
|
+
if (_filters[i].name === filterSpec.name) {
|
|
664
|
+
// Update perFilterOptions on the existing entry
|
|
665
|
+
if (perFilterOptions) {
|
|
666
|
+
_filters[i]._perFilterOptions = perFilterOptions;
|
|
667
|
+
}
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Store perFilterOptions alongside the spec (shallow copy to avoid mutating caller)
|
|
673
|
+
var entry = filterSpec;
|
|
674
|
+
if (perFilterOptions) {
|
|
675
|
+
entry = Object.assign({}, filterSpec);
|
|
676
|
+
entry._perFilterOptions = perFilterOptions;
|
|
677
|
+
}
|
|
678
|
+
_filters.push(entry);
|
|
679
|
+
},
|
|
680
|
+
|
|
681
|
+
removeFilter: function (nameOrSpec) {
|
|
682
|
+
var name = (typeof nameOrSpec === 'string') ? nameOrSpec
|
|
683
|
+
: (nameOrSpec && nameOrSpec.name);
|
|
684
|
+
_filters = _filters.filter(function (f) { return f.name !== name; });
|
|
685
|
+
if (name && _filterState[name]) { delete _filterState[name]; }
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
setSize: function (w, h) {
|
|
689
|
+
RendererProviderContract.requireProviderSize('p5Provider.setSize', w, h);
|
|
690
|
+
_width = w; _height = h;
|
|
691
|
+
if (_initialized && typeof resizeCanvas === 'function') { resizeCanvas(w, h); }
|
|
692
|
+
_notifyPose(); // the 2D map changes with the canvas (target null = its centre)
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
// Loop is owned by FrqtlApp — these are intentional no-ops.
|
|
696
|
+
start: function () {},
|
|
697
|
+
stop: function () {},
|
|
698
|
+
|
|
699
|
+
destroy: function () {
|
|
700
|
+
if (_p5Canvas && _p5Canvas.remove) { _p5Canvas.remove(); }
|
|
701
|
+
_initialized = false;
|
|
702
|
+
_filters = [];
|
|
703
|
+
_filterState = {};
|
|
704
|
+
_lastTickData = null;
|
|
705
|
+
_lastFilterTime = null;
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
raw: function () { return _p5Canvas; },
|
|
709
|
+
|
|
710
|
+
// renderCapabilities — OPTIONAL (see the module-level docstring above
|
|
711
|
+
// _renderCapabilitiesP5). Static per-provider metadata; no live-instance
|
|
712
|
+
// state needed, so the free function above is called unmodified.
|
|
713
|
+
renderCapabilities: _renderCapabilitiesP5,
|
|
714
|
+
|
|
715
|
+
// ── screenToWorld (OPTIONAL convenience capability, S5b) ────────────
|
|
716
|
+
// REPOINTED (2026-07-30) to consume the SAME 2D pose state the draw transform
|
|
717
|
+
// uses. Previously a fixed CSS→physics ratio; under pan/zoom that map must
|
|
718
|
+
// change, and if the pose lived anywhere but the camera authority two authorities
|
|
719
|
+
// would feed one mapping — the split-basis failure the 3D consolidation removed.
|
|
720
|
+
// At the default pose this is byte-identical to the old ratio scale (k === 1), so
|
|
721
|
+
// every existing drag/pick is unchanged until someone actually pans or zooms.
|
|
722
|
+
// `worldZ` still has no effect (2D has no depth transform; accepted for signature
|
|
723
|
+
// parity with three-provider.js).
|
|
724
|
+
screenToWorld: function (canvasCssX, canvasCssY, canvasCssWidth, canvasCssHeight /*, worldZ */) {
|
|
725
|
+
return Camera2dGeometry.screenToWorld2d(
|
|
726
|
+
_poseOrDefault(), _physW(), _physH(),
|
|
727
|
+
canvasCssX, canvasCssY, canvasCssWidth || _physW(), canvasCssHeight || _physH());
|
|
728
|
+
},
|
|
729
|
+
|
|
730
|
+
// ── getCameraControl (OPTIONAL capability) — the DEGENERATE orbit bag ──
|
|
731
|
+
// RENDERER_CONTRACT §1a.2. This provider DOES have camera pose control (pan +
|
|
732
|
+
// zoom), so it returns a handle rather than null: `getCameraControl()` returns
|
|
733
|
+
// null ONLY when a provider has no pose control AT ALL. The handle's `poseFields`
|
|
734
|
+
// declares exactly the axes this camera has — `distance` and `target`, and NO
|
|
735
|
+
// azimuth/elevation/roll — so a host hides the angular affordances by FIELD
|
|
736
|
+
// PRESENCE (the `roll`/`move()` capability idiom) and never asks "is this 2D?".
|
|
737
|
+
//
|
|
738
|
+
// All handle discipline carries over unchanged from the 3D camera: `setOrbit`
|
|
739
|
+
// applies a PATCH (only the keys mentioned), `getOrbit` returns the whole bag
|
|
740
|
+
// value-exact so a captured pose round-trips, a present non-finite value THROWS
|
|
741
|
+
// (throw-or-apply — a silent clamp would leave the camera in a plausible wrong
|
|
742
|
+
// place with no diagnostic), and `setPreset` is a FULL framing reset.
|
|
743
|
+
getCameraControl: function () {
|
|
744
|
+
var geom = Camera2dGeometry;
|
|
745
|
+
function _finite(v, name, where) {
|
|
746
|
+
if (typeof v === 'number' && isFinite(v)) { return v; }
|
|
747
|
+
throw new Error('p5-provider camera ' + where + ': ' + name +
|
|
748
|
+
' must be a finite number, got ' + String(v) +
|
|
749
|
+
' — throw-or-apply, never a silent clamp.');
|
|
750
|
+
}
|
|
751
|
+
function _presets() { return geom.PRESETS_2D(_physW(), _physH()); }
|
|
752
|
+
var ctl = {
|
|
753
|
+
// FERRIED from the geometry authority, exactly as the 3D handle ferries
|
|
754
|
+
// its own preset table — the provider holds no declarative data.
|
|
755
|
+
presets: Object.keys(_presets()),
|
|
756
|
+
poseFields: geom.POSE_FIELDS_2D(_physW(), _physH()),
|
|
757
|
+
setOrbit: function (spec) {
|
|
758
|
+
if (!spec) { return; }
|
|
759
|
+
// POINT SHAPE FIRST, at the ONE owner: `{x,y,z}` and `[x,y,z]` both
|
|
760
|
+
// arrive here as the canonical object form, and a malformed point has
|
|
761
|
+
// already thrown the same message the 3D handle throws.
|
|
762
|
+
spec = OrbitBag.normalizeOrbitPatch(spec);
|
|
763
|
+
var pose = _poseOrDefault();
|
|
764
|
+
if (spec.distance !== undefined) {
|
|
765
|
+
var d = _finite(spec.distance, 'distance', 'setOrbit');
|
|
766
|
+
if (d <= 0) {
|
|
767
|
+
throw new Error('p5-provider camera setOrbit: distance is the VISIBLE ' +
|
|
768
|
+
'HEIGHT in physics px and must be > 0, got ' + d + '. (FAIL-LOUD.)');
|
|
769
|
+
}
|
|
770
|
+
pose.distance = d;
|
|
771
|
+
}
|
|
772
|
+
// `target` is only touched when the spec MENTIONS it, so a zoom write
|
|
773
|
+
// never silently re-centres. An explicit `target:null` re-centres.
|
|
774
|
+
// The point is already normalized (object form or null); this camera
|
|
775
|
+
// PROJECTS it — it stores {x,y} and ignores z, which is what "the 2D
|
|
776
|
+
// pose is the degenerate orbit bag" means at the storage level.
|
|
777
|
+
if ('target' in spec) {
|
|
778
|
+
var t = spec.target;
|
|
779
|
+
pose.target = t ? { x: t.x, y: t.y } : null;
|
|
780
|
+
}
|
|
781
|
+
// An angular key is a REAL mistake here, not a no-op: silently
|
|
782
|
+
// ignoring it would let a 3D camera path path "run" in 2D and produce
|
|
783
|
+
// fabricated evidence. The handle declares what it has; asking for
|
|
784
|
+
// more fails loud.
|
|
785
|
+
// The angular axes are named once, at the pose-vocabulary owner.
|
|
786
|
+
var angular = OrbitBag.ANGULAR_FIELDS;
|
|
787
|
+
for (var i = 0; i < angular.length; i++) {
|
|
788
|
+
if (spec[angular[i]] !== undefined) {
|
|
789
|
+
throw new Error('p5-provider camera setOrbit: this camera declares no "' +
|
|
790
|
+
angular[i] + '" axis (2D pose is {distance, target}). Check ' +
|
|
791
|
+
'cameraControl.poseFields rather than assuming a mode. (FAIL-LOUD.)');
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
_notifyPose();
|
|
795
|
+
},
|
|
796
|
+
getOrbit: function () { return _poseBag(); },
|
|
797
|
+
// addPoseListener(fn) → unsubscribe — the POSE-CHANGE NOTICE: `fn(bag)` runs
|
|
798
|
+
// after EVERY pose write, with the getOrbit() bag (RENDERER_CONTRACT §1a.2).
|
|
799
|
+
addPoseListener: function (fn) {
|
|
800
|
+
if (!_poseNotice) { _poseNotice = OrbitBag.makePoseNotice(); }
|
|
801
|
+
return _poseNotice.add(fn);
|
|
802
|
+
},
|
|
803
|
+
setPreset: function (name) {
|
|
804
|
+
// A preset is a FULL framing reset — the reliable way back. A name this
|
|
805
|
+
// camera does not offer THROWS (a silent no-op would leave a saved or
|
|
806
|
+
// codegen'd preset doing nothing with no signal).
|
|
807
|
+
var all = _presets(), p = all[name];
|
|
808
|
+
if (!p) {
|
|
809
|
+
throw new Error('[p5-provider camera] setPreset("' + name + '"): this camera offers ' +
|
|
810
|
+
'only [' + Object.keys(all).join(', ') + '] (ctl.presets). (FAIL-LOUD.)');
|
|
811
|
+
}
|
|
812
|
+
ctl.setOrbit(p);
|
|
813
|
+
}
|
|
814
|
+
// No look()/move(): this camera has no angular axes and no dolly beyond
|
|
815
|
+
// `distance`. Their ABSENCE is the capability check (the move() precedent).
|
|
816
|
+
};
|
|
817
|
+
return ctl;
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
// ── projection (OPTIONAL capability — the scene's camera intrinsics) ──
|
|
821
|
+
// RENDERER_CONTRACT §1a.5 + §9: the DISPLAY provider is boot-fixed (swapping it
|
|
822
|
+
// is a reload) while OBSERVERS are live and own no projection — so this is
|
|
823
|
+
// scene-level and rides the SceneRecord, not each still. p5's 2D mode is the
|
|
824
|
+
// `affine2d` map — the per-still pan/zoom terms live on the still's own `pose`,
|
|
825
|
+
// which is where mutable state belongs.
|
|
826
|
+
projection: function () {
|
|
827
|
+
return { kind: 'affine2d' };
|
|
828
|
+
},
|
|
829
|
+
|
|
830
|
+
// ── captureStill (OPTIONAL capability — the `composite` tap executor) ──
|
|
831
|
+
// RENDERER_CONTRACT §1a.4. The 2D-canvas readback: `getImageData` is already
|
|
832
|
+
// RGBA, row-major, TOP-LEFT, and a 2D canvas PERSISTS between draws, so no flip
|
|
833
|
+
// and no re-render is needed (contrast the GL path). All bag assembly, the length
|
|
834
|
+
// check and the blank-buffer guard live in the shared owner. Returns a Promise per
|
|
835
|
+
// the frozen signature (a WebGPU backend's only readback is async).
|
|
836
|
+
//
|
|
837
|
+
// `filtersApplied` is read from THIS provider's own ordered `_filters` list — the
|
|
838
|
+
// one ordered authority, and the only one that sees a direct BYO `prov.filter()`.
|
|
839
|
+
captureStill: function () {
|
|
840
|
+
var common = CaptureStillCommon;
|
|
841
|
+
var bag = common.captureFrom2dCanvas(_getP5Canvas(), {
|
|
842
|
+
cssWidth: _physW(),
|
|
843
|
+
cssHeight: _physH(),
|
|
844
|
+
physicsW: _physW(),
|
|
845
|
+
physicsH: _physH(),
|
|
846
|
+
filtersApplied: _filters.map(function (f) { return f.name; }),
|
|
847
|
+
renderBaseCanvas: _lastRenderBaseCanvas,
|
|
848
|
+
filterTime: _lastFilterTime,
|
|
849
|
+
// The TickData these canvas pixels were drawn from → `renderedTick`.
|
|
850
|
+
composited: _lastTickData
|
|
851
|
+
});
|
|
852
|
+
return Promise.resolve(bag);
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
get initialized() { return _initialized; }
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
return provider;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
export { makeP5Provider, _renderCapabilitiesP5 };
|