phoria2 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +377 -377
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1052 -558
  7. package/dist/core.js.map +1 -1
  8. package/dist/edit.js +48 -48
  9. package/dist/edit.js.map +1 -1
  10. package/dist/explain.js +1 -1
  11. package/dist/gltf.js +207 -162
  12. package/dist/gltf.js.map +1 -1
  13. package/dist/motion.js +27 -27
  14. package/dist/motion.js.map +1 -1
  15. package/dist/particles.js +50 -50
  16. package/dist/particles.js.map +1 -1
  17. package/dist/passes.js +199 -97
  18. package/dist/passes.js.map +1 -1
  19. package/dist/phoria2.js +1 -1
  20. package/dist/picking.js +16 -21
  21. package/dist/picking.js.map +1 -1
  22. package/dist/snapshot.js +1 -1
  23. package/dist/tooltip.js +3 -3
  24. package/dist/tooltip.js.map +1 -1
  25. package/dist/types/View.d.ts +5 -0
  26. package/dist/types/geometry/Geometry.d.ts +6 -0
  27. package/dist/types/material/Material.d.ts +125 -0
  28. package/dist/types/material/Texture.d.ts +37 -5
  29. package/dist/types/webgl/WebGLRenderer.d.ts +6 -0
  30. package/dist/types/webgl/passes.d.ts +37 -13
  31. package/dist/types/webgl/resources.d.ts +24 -2
  32. package/dist/types/webgl/shaders.d.ts +4 -2
  33. package/llms-full.txt +96 -7
  34. package/package.json +1 -1
  35. package/schema/scene.schema.json +127 -0
  36. package/src/View.js +4 -2
  37. package/src/geometry/Geometry.js +8 -1
  38. package/src/gltf/load.js +50 -6
  39. package/src/material/Material.js +104 -19
  40. package/src/material/Texture.js +116 -11
  41. package/src/watch.js +19 -0
  42. package/src/webgl/WebGLRenderer.js +51 -12
  43. package/src/webgl/passes.js +190 -32
  44. package/src/webgl/picking.js +0 -1
  45. package/src/webgl/resources.js +89 -8
  46. package/src/webgl/shaders.js +350 -48
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":["describe","fail","STYLE","GAP","Tooltip","content","style","className","hit","render","x","y","w","h","left","top","box","title","rows","entries","name","value","n","v"],"sources":["../src/tooltip.js"],"sourcesContent":["// The hover tooltip: one element that follows the pointer and shows what the app says about the\n// thing under it. Its default look is in a :where() rule, which has no specificity, so any CSS for\n// .phoria-tooltip overrides it.\nimport { describe, fail } from './validate.js'\n\n/** @import { Hit } from './Raycaster.js' */\n\n/**\n * What a tooltip function returns: text (new lines kept), an element, or a title with rows of\n * name/value pairs. null (or '') shows nothing.\n * @typedef {string | number | Node | { title?: string, rows?: Record<string, unknown> | Array<[string, unknown]> } | null | undefined | false} TooltipContent\n */\n\nconst STYLE = `\n:where(.phoria-tooltip) { position: fixed; left: 0; top: 0; z-index: 1000; pointer-events: none; max-width: 320px;\n padding: 6px 10px; border-radius: 6px; background: rgba(20, 20, 24, 0.92); color: #fff;\n font: 12px/1.45 system-ui, sans-serif; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); white-space: pre-line; }\n:where(.phoria-tooltip-title) { font-weight: 600; }\n:where(.phoria-tooltip-rows) { display: grid; grid-template-columns: auto auto; column-gap: 12px; }\n:where(.phoria-tooltip-rows > :nth-child(odd)) { opacity: 0.7; }\n:where(.phoria-tooltip-rows > :nth-child(even)) { text-align: right; font-variant-numeric: tabular-nums; }\n`\n// space between the pointer and the tooltip, in CSS pixels\nconst GAP = 14\n\nexport class Tooltip {\n /** @type {HTMLDivElement} */ el\n /** @type {(hit: Hit) => TooltipContent} */ content\n\n /** @param {(hit: Hit) => TooltipContent} content @param {string} [className] */\n constructor(content, className = '') {\n if (typeof content !== 'function') fail('View', `tooltip() takes a function (hit) => text, got ${describe(content)}`)\n this.content = content\n if (!document.getElementById('phoria-tooltip-style')) {\n const style = document.createElement('style')\n style.id = 'phoria-tooltip-style'\n style.textContent = STYLE\n document.head.prepend(style)\n }\n this.el = document.createElement('div')\n this.el.className = `phoria-tooltip${className ? ` ${className}` : ''}`\n this.el.setAttribute('role', 'tooltip')\n this.el.hidden = true\n document.body.append(this.el)\n }\n\n /** Show what the content function says about a hit, near the pointer. @param {Hit} hit @param {number} x @param {number} y */\n show(hit, x, y) {\n const content = this.content(hit)\n if (content === null || content === undefined || content === false || content === '') return this.hide()\n this.el.replaceChildren(render(content))\n this.el.hidden = false\n this.move(x, y)\n }\n\n /** Follow the pointer, staying inside the window. @param {number} x @param {number} y client pixels */\n move(x, y) {\n if (this.el.hidden) return\n const w = this.el.offsetWidth, h = this.el.offsetHeight\n const left = x + GAP + w > window.innerWidth ? x - GAP - w : x + GAP\n const top = y + GAP + h > window.innerHeight ? y - GAP - h : y + GAP\n this.el.style.transform = `translate(${Math.round(Math.max(0, left))}px, ${Math.round(Math.max(0, top))}px)`\n }\n\n hide() {\n this.el.hidden = true\n }\n\n dispose() {\n this.el.remove()\n }\n}\n\n/** @param {TooltipContent} content @returns {Node} */\nfunction render(content) {\n if (typeof content === 'string' || typeof content === 'number') return document.createTextNode(String(content))\n if (content instanceof Node) return content\n if (typeof content === 'object' && content && ('title' in content || 'rows' in content)) {\n const box = document.createDocumentFragment()\n if (content.title !== undefined) {\n const title = document.createElement('div')\n title.className = 'phoria-tooltip-title'\n title.textContent = String(content.title)\n box.append(title)\n }\n if (content.rows) {\n const rows = document.createElement('div')\n rows.className = 'phoria-tooltip-rows'\n const entries = Array.isArray(content.rows) ? content.rows : Object.entries(content.rows)\n for (const [name, value] of entries) {\n const n = document.createElement('span'), v = document.createElement('span')\n n.textContent = String(name)\n v.textContent = typeof value === 'number' ? value.toLocaleString() : String(value)\n rows.append(n, v)\n }\n box.append(rows)\n }\n return box\n }\n return fail('View', `a tooltip function returns text, an element or { title, rows }, got ${describe(content)}`)\n}\n"],"mappings":";;AAaA,IAAME,IAAQ,2oBAURC,IAAM,IAECC,IAAb,MAAqB;CACW;CACc;CAG5C,YAAY,GAAS,IAAY,IAAI;EAGnC,IAFI,OAAOC,KAAY,cAAYJ,EAAK,QAAQ,iDAAiDD,EAASK,CAAO,GAAG,GACpH,KAAK,UAAUA,GACX,CAAC,SAAS,eAAe,sBAAsB,GAAG;GACpD,IAAMC,IAAQ,SAAS,cAAc,OAAO;GAG5C,AAFA,EAAM,KAAK,wBACX,EAAM,cAAcJ,GACpB,SAAS,KAAK,QAAQI,CAAK;EAC7B;EAKA,AAJA,KAAK,KAAK,SAAS,cAAc,KAAK,GACtC,KAAK,GAAG,YAAY,iBAAiBC,IAAY,IAAIA,MAAc,MACnE,KAAK,GAAG,aAAa,QAAQ,SAAS,GACtC,KAAK,GAAG,SAAS,IACjB,SAAS,KAAK,OAAO,KAAK,EAAE;CAC9B;CAGA,KAAK,GAAK,GAAG,GAAG;EACd,IAAMF,IAAU,KAAK,QAAQG,CAAG;EAChC,IAAIH,KAAY,QAAiCA,MAAY,MAASA,MAAY,IAAI,OAAO,KAAK,KAAK;EAGvG,AAFA,KAAK,GAAG,gBAAgBI,EAAOJ,CAAO,CAAC,GACvC,KAAK,GAAG,SAAS,IACjB,KAAK,KAAKK,GAAGC,CAAC;CAChB;CAGA,KAAK,GAAG,GAAG;EACT,IAAI,KAAK,GAAG,QAAQ;EACpB,IAAMC,IAAI,KAAK,GAAG,aAAaC,IAAI,KAAK,GAAG,cACrCC,IAAOJ,IAAIP,IAAMS,IAAI,OAAO,aAAaF,IAAIP,IAAMS,IAAIF,IAAIP,GAC3DY,IAAMJ,IAAIR,IAAMU,IAAI,OAAO,cAAcF,IAAIR,IAAMU,IAAIF,IAAIR;EACjE,KAAK,GAAG,MAAM,YAAY,aAAa,KAAK,MAAM,KAAK,IAAI,GAAGW,CAAI,CAAC,EAAE,MAAM,KAAK,MAAM,KAAK,IAAI,GAAGC,CAAG,CAAC,EAAE;CAC1G;CAEA,OAAO;EACL,KAAK,GAAG,SAAS;CACnB;CAEA,UAAU;EACR,KAAK,GAAG,OAAO;CACjB;AACF;AAGA,SAASN,EAAO,GAAS;CACvB,IAAI,OAAOJ,KAAY,YAAY,OAAOA,KAAY,UAAU,OAAO,SAAS,eAAe,OAAOA,CAAO,CAAC;CAC9G,IAAIA,aAAmB,MAAM,OAAOA;CACpC,IAAI,OAAOA,KAAY,YAAYA,MAAY,WAAWA,KAAW,UAAUA,IAAU;EACvF,IAAMW,IAAM,SAAS,uBAAuB;EAC5C,IAAIX,EAAQ,UAAU,KAAA,GAAW;GAC/B,IAAMY,IAAQ,SAAS,cAAc,KAAK;GAG1C,AAFA,EAAM,YAAY,wBAClB,EAAM,cAAc,OAAOZ,EAAQ,KAAK,GACxCW,EAAI,OAAOC,CAAK;EAClB;EACA,IAAIZ,EAAQ,MAAM;GAChB,IAAMa,IAAO,SAAS,cAAc,KAAK;GACzC,EAAK,YAAY;GACjB,IAAMC,IAAU,MAAM,QAAQd,EAAQ,IAAI,IAAIA,EAAQ,OAAO,OAAO,QAAQA,EAAQ,IAAI;GACxF,KAAK,IAAM,CAACe,GAAMC,MAAUF,GAAS;IACnC,IAAMG,IAAI,SAAS,cAAc,MAAM,GAAGC,IAAI,SAAS,cAAc,MAAM;IAG3E,AAFA,EAAE,cAAc,OAAOH,CAAI,GAC3B,EAAE,cAAc,OAAOC,KAAU,WAAWA,EAAM,eAAe,IAAI,OAAOA,CAAK,GACjFH,EAAK,OAAOI,GAAGC,CAAC;GAClB;GACA,EAAI,OAAOL,CAAI;EACjB;EACA,OAAOF;CACT;CACA,OAAOf,EAAK,QAAQ,uEAAuED,EAASK,CAAO,GAAG;AAChH"}
1
+ {"version":3,"file":"tooltip.js","names":["fail","describe","STYLE","GAP","Tooltip","content","style","className","hit","render","x","y","w","h","left","top","box","title","rows","entries","name","value","n","v"],"sources":["../src/tooltip.js"],"sourcesContent":["// The hover tooltip: one element that follows the pointer and shows what the app says about the\n// thing under it. Its default look is in a :where() rule, which has no specificity, so any CSS for\n// .phoria-tooltip overrides it.\nimport { describe, fail } from './validate.js'\n\n/** @import { Hit } from './Raycaster.js' */\n\n/**\n * What a tooltip function returns: text (new lines kept), an element, or a title with rows of\n * name/value pairs. null (or '') shows nothing.\n * @typedef {string | number | Node | { title?: string, rows?: Record<string, unknown> | Array<[string, unknown]> } | null | undefined | false} TooltipContent\n */\n\nconst STYLE = `\n:where(.phoria-tooltip) { position: fixed; left: 0; top: 0; z-index: 1000; pointer-events: none; max-width: 320px;\n padding: 6px 10px; border-radius: 6px; background: rgba(20, 20, 24, 0.92); color: #fff;\n font: 12px/1.45 system-ui, sans-serif; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); white-space: pre-line; }\n:where(.phoria-tooltip-title) { font-weight: 600; }\n:where(.phoria-tooltip-rows) { display: grid; grid-template-columns: auto auto; column-gap: 12px; }\n:where(.phoria-tooltip-rows > :nth-child(odd)) { opacity: 0.7; }\n:where(.phoria-tooltip-rows > :nth-child(even)) { text-align: right; font-variant-numeric: tabular-nums; }\n`\n// space between the pointer and the tooltip, in CSS pixels\nconst GAP = 14\n\nexport class Tooltip {\n /** @type {HTMLDivElement} */ el\n /** @type {(hit: Hit) => TooltipContent} */ content\n\n /** @param {(hit: Hit) => TooltipContent} content @param {string} [className] */\n constructor(content, className = '') {\n if (typeof content !== 'function') fail('View', `tooltip() takes a function (hit) => text, got ${describe(content)}`)\n this.content = content\n if (!document.getElementById('phoria-tooltip-style')) {\n const style = document.createElement('style')\n style.id = 'phoria-tooltip-style'\n style.textContent = STYLE\n document.head.prepend(style)\n }\n this.el = document.createElement('div')\n this.el.className = `phoria-tooltip${className ? ` ${className}` : ''}`\n this.el.setAttribute('role', 'tooltip')\n this.el.hidden = true\n document.body.append(this.el)\n }\n\n /** Show what the content function says about a hit, near the pointer. @param {Hit} hit @param {number} x @param {number} y */\n show(hit, x, y) {\n const content = this.content(hit)\n if (content === null || content === undefined || content === false || content === '') return this.hide()\n this.el.replaceChildren(render(content))\n this.el.hidden = false\n this.move(x, y)\n }\n\n /** Follow the pointer, staying inside the window. @param {number} x @param {number} y client pixels */\n move(x, y) {\n if (this.el.hidden) return\n const w = this.el.offsetWidth, h = this.el.offsetHeight\n const left = x + GAP + w > window.innerWidth ? x - GAP - w : x + GAP\n const top = y + GAP + h > window.innerHeight ? y - GAP - h : y + GAP\n this.el.style.transform = `translate(${Math.round(Math.max(0, left))}px, ${Math.round(Math.max(0, top))}px)`\n }\n\n hide() {\n this.el.hidden = true\n }\n\n dispose() {\n this.el.remove()\n }\n}\n\n/** @param {TooltipContent} content @returns {Node} */\nfunction render(content) {\n if (typeof content === 'string' || typeof content === 'number') return document.createTextNode(String(content))\n if (content instanceof Node) return content\n if (typeof content === 'object' && content && ('title' in content || 'rows' in content)) {\n const box = document.createDocumentFragment()\n if (content.title !== undefined) {\n const title = document.createElement('div')\n title.className = 'phoria-tooltip-title'\n title.textContent = String(content.title)\n box.append(title)\n }\n if (content.rows) {\n const rows = document.createElement('div')\n rows.className = 'phoria-tooltip-rows'\n const entries = Array.isArray(content.rows) ? content.rows : Object.entries(content.rows)\n for (const [name, value] of entries) {\n const n = document.createElement('span'), v = document.createElement('span')\n n.textContent = String(name)\n v.textContent = typeof value === 'number' ? value.toLocaleString() : String(value)\n rows.append(n, v)\n }\n box.append(rows)\n }\n return box\n }\n return fail('View', `a tooltip function returns text, an element or { title, rows }, got ${describe(content)}`)\n}\n"],"mappings":";;AAaA,IAAME,IAAQ,2oBAURC,IAAM,IAECC,IAAb,MAAqB;CACW;CACc;CAG5C,YAAY,GAAS,IAAY,IAAI;EAGnC,IAFI,OAAOC,KAAY,cAAYL,EAAK,QAAQ,iDAAiDC,EAASI,CAAO,GAAG,GACpH,KAAK,UAAUA,GACX,CAAC,SAAS,eAAe,sBAAsB,GAAG;GACpD,IAAMC,IAAQ,SAAS,cAAc,OAAO;GAG5C,AAFA,EAAM,KAAK,wBACX,EAAM,cAAcJ,GACpB,SAAS,KAAK,QAAQI,CAAK;EAC7B;EAKA,AAJA,KAAK,KAAK,SAAS,cAAc,KAAK,GACtC,KAAK,GAAG,YAAY,iBAAiBC,IAAY,IAAIA,MAAc,MACnE,KAAK,GAAG,aAAa,QAAQ,SAAS,GACtC,KAAK,GAAG,SAAS,IACjB,SAAS,KAAK,OAAO,KAAK,EAAE;CAC9B;CAGA,KAAK,GAAK,GAAG,GAAG;EACd,IAAMF,IAAU,KAAK,QAAQG,CAAG;EAChC,IAAIH,KAAY,QAAiCA,MAAY,MAASA,MAAY,IAAI,OAAO,KAAK,KAAK;EAGvG,AAFA,KAAK,GAAG,gBAAgBI,EAAOJ,CAAO,CAAC,GACvC,KAAK,GAAG,SAAS,IACjB,KAAK,KAAKK,GAAGC,CAAC;CAChB;CAGA,KAAK,GAAG,GAAG;EACT,IAAI,KAAK,GAAG,QAAQ;EACpB,IAAMC,IAAI,KAAK,GAAG,aAAaC,IAAI,KAAK,GAAG,cACrCC,IAAOJ,IAAIP,IAAMS,IAAI,OAAO,aAAaF,IAAIP,IAAMS,IAAIF,IAAIP,GAC3DY,IAAMJ,IAAIR,IAAMU,IAAI,OAAO,cAAcF,IAAIR,IAAMU,IAAIF,IAAIR;EACjE,KAAK,GAAG,MAAM,YAAY,aAAa,KAAK,MAAM,KAAK,IAAI,GAAGW,CAAI,CAAC,EAAE,MAAM,KAAK,MAAM,KAAK,IAAI,GAAGC,CAAG,CAAC,EAAE;CAC1G;CAEA,OAAO;EACL,KAAK,GAAG,SAAS;CACnB;CAEA,UAAU;EACR,KAAK,GAAG,OAAO;CACjB;AACF;AAGA,SAASN,EAAO,GAAS;CACvB,IAAI,OAAOJ,KAAY,YAAY,OAAOA,KAAY,UAAU,OAAO,SAAS,eAAe,OAAOA,CAAO,CAAC;CAC9G,IAAIA,aAAmB,MAAM,OAAOA;CACpC,IAAI,OAAOA,KAAY,YAAYA,MAAY,WAAWA,KAAW,UAAUA,IAAU;EACvF,IAAMW,IAAM,SAAS,uBAAuB;EAC5C,IAAIX,EAAQ,UAAU,KAAA,GAAW;GAC/B,IAAMY,IAAQ,SAAS,cAAc,KAAK;GAG1C,AAFA,EAAM,YAAY,wBAClB,EAAM,cAAc,OAAOZ,EAAQ,KAAK,GACxCW,EAAI,OAAOC,CAAK;EAClB;EACA,IAAIZ,EAAQ,MAAM;GAChB,IAAMa,IAAO,SAAS,cAAc,KAAK;GACzC,EAAK,YAAY;GACjB,IAAMC,IAAU,MAAM,QAAQd,EAAQ,IAAI,IAAIA,EAAQ,OAAO,OAAO,QAAQA,EAAQ,IAAI;GACxF,KAAK,IAAM,CAACe,GAAMC,MAAUF,GAAS;IACnC,IAAMG,IAAI,SAAS,cAAc,MAAM,GAAGC,IAAI,SAAS,cAAc,MAAM;IAG3E,AAFA,EAAE,cAAc,OAAOH,CAAI,GAC3B,EAAE,cAAc,OAAOC,KAAU,WAAWA,EAAM,eAAe,IAAI,OAAOA,CAAK,GACjFH,EAAK,OAAOI,GAAGC,CAAC;GAClB;GACA,EAAI,OAAOL,CAAI;EACjB;EACA,OAAOF;CACT;CACA,OAAOhB,EAAK,QAAQ,uEAAuEC,EAASI,CAAO,GAAG;AAChH"}
@@ -73,6 +73,11 @@ export type ViewOptions = {
73
73
  * (default true)
74
74
  */
75
75
  antialias?: boolean;
76
+ /**
77
+ * soft-edged outlines with round corners (default: as antialias); change it
78
+ * any time with view.renderer.outlineAntialias
79
+ */
80
+ outlineAntialias?: boolean;
76
81
  /**
77
82
  * which WebGL context draws it: 'own' gives the canvas one of
78
83
  * its own; 'shared' draws with the page's one shared context and copies the picture over, so a page can hold
@@ -12,6 +12,11 @@ export type GeometryOptions = {
12
12
  * uv per vertex - glTF convention, (0, 0) is the image's top left
13
13
  */
14
14
  uvs?: ArrayLike<number>;
15
+ /**
16
+ * a second uv per vertex, for maps that need every surface on its own
17
+ * patch of texture - baked occlusion and light maps (a texture's uvSet: 1 reads it)
18
+ */
19
+ uvs2?: ArrayLike<number>;
15
20
  /**
16
21
  * rgb per vertex, sRGB 0-1 like every colour input
17
22
  */
@@ -31,6 +36,7 @@ export declare class Geometry {
31
36
  static OPTIONS: string[];
32
37
  /** @type {Float32Array} */ positions: Float32Array;
33
38
  /** @type {Float32Array | null} */ uvs: Float32Array | null;
39
+ /** A second UV set, for baked occlusion and light maps. @type {Float32Array | null} */ uvs2: Float32Array | null;
34
40
  /** @type {Float32Array | null} */ colors: Float32Array | null;
35
41
  /** @type {Uint16Array | Uint32Array | null} */ indices: Uint16Array | Uint32Array | null;
36
42
  /**
@@ -76,6 +76,20 @@ export type MaterialOptions = {
76
76
  * multiplies the emissive colour
77
77
  */
78
78
  emissiveMap?: MapInput | null;
79
+ /**
80
+ * light baked into a texture, added as light from all round - usually on
81
+ * the second UV set (uvSet: 1)
82
+ */
83
+ lightMap?: MapInput | null;
84
+ /**
85
+ * (default 1)
86
+ */
87
+ lightMapIntensity?: number;
88
+ /**
89
+ * this material's share of the environment's light and reflections:
90
+ * lower for a matte floor in a shiny showroom, higher to make one thing catch the light (default 1)
91
+ */
92
+ environmentIntensity?: number;
79
93
  /**
80
94
  * glow colour (default black)
81
95
  */
@@ -84,6 +98,74 @@ export type MaterialOptions = {
84
98
  * (default 1)
85
99
  */
86
100
  emissiveIntensity?: number;
101
+ /**
102
+ * 0-1: how much light passes through, as through glass (default 0). Unlike
103
+ * opacity, which fades the whole surface, glass keeps its reflections, and shows less of what is behind it
104
+ * towards its edges. Its colour shows in proportion to 1 - transmission
105
+ */
106
+ transmission?: number;
107
+ /**
108
+ * 0-1: a clear glossy layer over the surface - car paint, lacquer, varnish (default 0)
109
+ */
110
+ clearcoat?: number;
111
+ /**
112
+ * 0-1: how glossy the coat is, 0 a mirror (default 0)
113
+ */
114
+ clearcoatRoughness?: number;
115
+ /**
116
+ * 0-1: the soft bright rim of cloth at grazing angles - velvet, felt, upholstery (default 0)
117
+ */
118
+ sheen?: number;
119
+ /**
120
+ * the sheen's colour (default white)
121
+ */
122
+ sheenColor?: ColorInput;
123
+ /**
124
+ * 0-1: a thin bright rim at 0, a broad soft glow at 1 (default 0.5)
125
+ */
126
+ sheenRoughness?: number;
127
+ /**
128
+ * index of refraction, 1 or more: how strongly a non-metal reflects - water 1.33, glass and
129
+ * plastic 1.5 (the default), diamond 2.4
130
+ */
131
+ ior?: number;
132
+ /**
133
+ * 0-1: scales a non-metal's reflections, 0 for none - chalk, clay (default 1)
134
+ */
135
+ specularIntensity?: number;
136
+ /**
137
+ * tints a non-metal's reflections face on (default white)
138
+ */
139
+ specularColor?: ColorInput;
140
+ /**
141
+ * 0-1: brushed metal - highlights drawn out along the brushing, which runs along
142
+ * the UVs' u (around the mesh where it has no UVs) (default 0)
143
+ */
144
+ anisotropy?: number;
145
+ /**
146
+ * turns the brushing from the UVs' u, in radians (default 0)
147
+ */
148
+ anisotropyRotation?: number;
149
+ /**
150
+ * 0-1: light through thin surfaces, lighting them from behind - leaves, paper,
151
+ * lampshades, curtains (default 0)
152
+ */
153
+ translucency?: number;
154
+ /**
155
+ * tints the light that comes through (default white)
156
+ */
157
+ translucencyColor?: ColorInput;
158
+ /**
159
+ * a picture of a lit sphere that gives the whole look - clay, wax, chrome,
160
+ * a painted ball - picked by which way the surface faces the camera. Lights, shadows and the environment are
161
+ * ignored; the colour and colour map multiply it
162
+ */
163
+ matcap?: MapInput | null;
164
+ /**
165
+ * cartoon shading in this many flat tones (2 or more) with a hard-edged highlight;
166
+ * 0 (the default) is smooth
167
+ */
168
+ toon?: number;
87
169
  /**
88
170
  * flat colour that ignores lights (default false)
89
171
  */
@@ -144,7 +226,17 @@ export declare class Material {
144
226
  side: string;
145
227
  wireframe: string;
146
228
  shininess: string;
229
+ envMapIntensity: string;
230
+ reflectivityScale: string;
231
+ matcapMap: string;
232
+ gradientMap: string;
233
+ cel: string;
234
+ celShading: string;
147
235
  specular: string;
236
+ reflectivity: string;
237
+ anisotropyStrength: string;
238
+ diffuseTransmission: string;
239
+ subsurface: string;
148
240
  colour: string;
149
241
  emissiveColor: string;
150
242
  alpha: string;
@@ -159,6 +251,19 @@ export declare class Material {
159
251
  /** @type {number} */ roughness: number;
160
252
  /** @type {number} */ metalness: number;
161
253
  /** @type {number} */ emissiveIntensity: number;
254
+ /** How much light passes through, as through glass: 0-1. @type {number} */ transmission: number;
255
+ /** A clear glossy layer over the surface: 0-1. @type {number} */ clearcoat: number;
256
+ /** How glossy the coat is, 0 a mirror. @type {number} */ clearcoatRoughness: number;
257
+ /** The soft bright rim of cloth: 0-1. @type {number} */ sheen: number;
258
+ /** A thin bright rim at 0, a broad soft glow at 1. @type {number} */ sheenRoughness: number;
259
+ /** Index of refraction: how strongly a non-metal reflects (1.5). @type {number} */ ior: number;
260
+ /** Scales a non-metal's reflections, 0-1. @type {number} */ specularIntensity: number;
261
+ /** Brushed metal: 0-1. @type {number} */ anisotropy: number;
262
+ /** Turns the brushing from the UVs' u, in radians. @type {number} */ anisotropyRotation: number;
263
+ /** Light through thin surfaces: 0-1. @type {number} */ translucency: number;
264
+ /** Cartoon shading in this many flat tones; 0 is smooth. @type {number} */ toon: number;
265
+ /** @type {number} */ lightMapIntensity: number;
266
+ /** This material's share of the environment's light and reflections. @type {number} */ environmentIntensity: number;
162
267
  /** @type {boolean} */ unlit: boolean;
163
268
  /** @type {boolean} */ flatShading: boolean;
164
269
  /** @type {boolean} */ doubleSided: boolean;
@@ -180,6 +285,18 @@ export declare class Material {
180
285
  get emissive(): Color;
181
286
  /** @param {ColorInput} v */
182
287
  set emissive(v: ColorInput);
288
+ /** The sheen's colour; assign any colour input. @returns {Color} */
289
+ get sheenColor(): Color;
290
+ /** @param {ColorInput} v */
291
+ set sheenColor(v: ColorInput);
292
+ /** Tints a non-metal's reflections face on; assign any colour input. @returns {Color} */
293
+ get specularColor(): Color;
294
+ /** @param {ColorInput} v */
295
+ set specularColor(v: ColorInput);
296
+ /** Tints the light that comes through a translucent surface; assign any colour input. @returns {Color} */
297
+ get translucencyColor(): Color;
298
+ /** @param {ColorInput} v */
299
+ set translucencyColor(v: ColorInput);
183
300
  /** @returns {Texture | null} */
184
301
  get map(): Texture | null;
185
302
  /** @param {MapInput | null} v */
@@ -208,6 +325,14 @@ export declare class Material {
208
325
  get emissiveMap(): Texture | null;
209
326
  /** @param {MapInput | null} v */
210
327
  set emissiveMap(v: MapInput | null);
328
+ /** Light baked into a texture. @returns {Texture | null} */
329
+ get lightMap(): Texture | null;
330
+ /** @param {MapInput | null} v */
331
+ set lightMap(v: MapInput | null);
332
+ /** A picture of a lit sphere that gives the whole look. @returns {Texture | null} */
333
+ get matcap(): Texture | null;
334
+ /** @param {MapInput | null} v */
335
+ set matcap(v: MapInput | null);
211
336
  /** The maps that are set, by slot. @returns {[string, Texture][]} */
212
337
  get textures(): [string, Texture][];
213
338
  /** Rendered in the transparent pass: an opacity below 1, additive or multiply blending, or set for maps with soft alpha. @returns {boolean} */
@@ -11,7 +11,8 @@ export type TextureOptions = {
11
11
  image?: TextureImage;
12
12
  /**
13
13
  * image to load (starts loading immediately in a browser); a .hdr file loads as
14
- * linear HDR radiance, for scene.environment
14
+ * linear HDR radiance, for scene.environment; a video (.mp4, .webm, .ogv, .mov, .m4v) plays muted and on a
15
+ * loop, and texture.image is its <video> to pause, seek or unmute
15
16
  */
16
17
  url?: string;
17
18
  /**
@@ -31,12 +32,26 @@ export type TextureOptions = {
31
32
  * [u, v] (default [0, 0])
32
33
  */
33
34
  offset?: number[];
35
+ /**
36
+ * turns the image on the surface, in radians, about `center` (default 0)
37
+ */
38
+ rotation?: number;
39
+ /**
40
+ * [u, v] the point rotation turns about, in repeated UVs (default [0, 0], the
41
+ * corner, as glTF has it; [0.5, 0.5] for the middle of an unrepeated image)
42
+ */
43
+ center?: number[];
44
+ /**
45
+ * which UV set to read: 0 the first, 1 the second (Geometry uvs2) - baked occlusion
46
+ * and light maps usually come on the second (default 0)
47
+ */
48
+ uvSet?: 0 | 1;
34
49
  /**
35
50
  * (default 'linear')
36
51
  */
37
52
  filter?: 'linear' | 'nearest';
38
53
  /**
39
- * (default true)
54
+ * (default true; false for a video, which would make them every frame)
40
55
  */
41
56
  mipmaps?: boolean;
42
57
  /**
@@ -49,7 +64,10 @@ export type TextureOptions = {
49
64
  */
50
65
  flipY?: boolean;
51
66
  };
52
- /** An image for a material map. Canvas and video textures: set `needsUpdate()` after drawing. */
67
+ /**
68
+ * An image for a material map. A video's frames upload by themselves as it plays; after drawing on
69
+ * a canvas, call `needsUpdate()`.
70
+ */
53
71
  export declare class Texture {
54
72
  #private;
55
73
  /** The options it takes, and hints for names people reach for instead. */
@@ -59,15 +77,22 @@ export declare class Texture {
59
77
  encoding: string;
60
78
  wrapS: string;
61
79
  maxAnisotropy: string;
80
+ channel: string;
81
+ texCoord: string;
82
+ scale: string;
62
83
  };
63
- /** @type {TextureImage | null} null until loaded */
64
- image: TextureImage | null;
65
84
  /** @type {string | null} */
66
85
  url: string | null;
67
86
  /** @type {'repeat' | 'clamp' | 'mirror'} */
68
87
  wrap: 'repeat' | 'clamp' | 'mirror';
69
88
  repeat: number[];
70
89
  offset: number[];
90
+ /** Turns the image on the surface, in radians, about `center`. */
91
+ rotation: number;
92
+ /** [u, v] the point rotation turns about, in repeated UVs. */
93
+ center: number[];
94
+ /** Which UV set it reads: 0 the first, 1 the second (Geometry uvs2). @type {0 | 1} */
95
+ uvSet: 0 | 1;
71
96
  /** @type {'linear' | 'nearest'} */
72
97
  filter: 'linear' | 'nearest';
73
98
  mipmaps: boolean;
@@ -79,6 +104,13 @@ export declare class Texture {
79
104
  /** @param {TextureOptions | string} [options] options, or just a url */
80
105
  constructor(options?: TextureOptions | string);
81
106
  /** Call after redrawing a canvas or advancing a video. @returns {this} */
107
+ /**
108
+ * The pixels: null until loaded. A <video>'s frames upload by themselves while it plays.
109
+ * @returns {TextureImage | null}
110
+ */
111
+ get image(): TextureImage | null;
112
+ /** @param {TextureImage | null} v */
113
+ set image(v: TextureImage | null);
82
114
  /** 'srgb' for colour (decoded to linear by the GPU), 'linear' for data such as normals and roughness. Call needsUpdate() after changing it. @returns {'srgb' | 'linear'} */
83
115
  get colorSpace(): 'srgb' | 'linear';
84
116
  set colorSpace(v: 'srgb' | 'linear');
@@ -29,6 +29,10 @@ export type WebGLRendererOptions = {
29
29
  * multisampling (default true; a shared context always has it)
30
30
  */
31
31
  antialias?: boolean;
32
+ /**
33
+ * soft-edged outlines with round corners (default: as antialias)
34
+ */
35
+ outlineAntialias?: boolean;
32
36
  /**
33
37
  * how lit colours are mapped to the screen: 'neutral' keeps colours
34
38
  * true (Khronos PBR Neutral, the default), 'aces' is filmic, 'none' clips
@@ -80,6 +84,8 @@ export declare class WebGLRenderer {
80
84
  /** @type {ToneMapping} */ toneMapping: ToneMapping;
81
85
  exposure: number;
82
86
  pixelRatio: number;
87
+ /** Outlines with soft edges and round corners, or hard pixel steps; takes effect on the next frame. */
88
+ outlineAntialias: boolean;
83
89
  /** Counts from the last frame and the GPU caches. */
84
90
  info: {
85
91
  drawCalls: number;
@@ -24,6 +24,30 @@ export type ShadowMap = {
24
24
  sampler: string;
25
25
  texture: WebGLTexture;
26
26
  };
27
+ export type Target = {
28
+ texture: WebGLTexture;
29
+ framebuffer: WebGLFramebuffer;
30
+ };
31
+ export type OutlineTargets = {
32
+ width: number;
33
+ height: number;
34
+ smooth: boolean;
35
+ mask: Target;
36
+ wide: Target;
37
+ coverage: Target | null;
38
+ msaa: {
39
+ framebuffer: WebGLFramebuffer;
40
+ renderbuffers: WebGLRenderbuffer[];
41
+ } | null;
42
+ };
43
+ export type OutlineMask = {
44
+ mask: WebGLTexture;
45
+ wide: WebGLTexture;
46
+ coverage: WebGLTexture | null;
47
+ radius: number;
48
+ width: number;
49
+ height: number;
50
+ };
27
51
  /**
28
52
  * @typedef {object} Environment
29
53
  * @property {WebGLTexture} source the HDR image, mipmapped
@@ -38,6 +62,12 @@ export type ShadowMap = {
38
62
  * a pass that returns one of these.
39
63
  * @typedef {{ define: string, uniforms: Record<string, unknown>, sampler: string, texture: WebGLTexture }} ShadowMap
40
64
  */
65
+ /**
66
+ * @typedef {{ texture: WebGLTexture, framebuffer: WebGLFramebuffer }} Target
67
+ * @typedef {{ width: number, height: number, smooth: boolean, mask: Target, wide: Target, coverage: Target | null,
68
+ * msaa: { framebuffer: WebGLFramebuffer, renderbuffers: WebGLRenderbuffer[] } | null }} OutlineTargets
69
+ * @typedef {{ mask: WebGLTexture, wide: WebGLTexture, coverage: WebGLTexture | null, radius: number, width: number, height: number }} OutlineMask
70
+ */
41
71
  export declare class Passes {
42
72
  #private;
43
73
  gl: WebGL2RenderingContext;
@@ -88,25 +118,19 @@ export declare class Passes {
88
118
  * The first half of drawing outlines, before the scene: a mask of the outlined subtrees - each
89
119
  * pixel they cover in their colour, with the line's width in alpha - then that mask widened
90
120
  * sideways. outlineComposite() widens it up and down and draws the ring left round each mask.
121
+ * Smooth outlines draw the mask multisampled, with how much of each pixel it covers, and measure
122
+ * the ring from the shape's edge; otherwise each pixel is in or out.
91
123
  * @param {FrameOutline[]} outlines @param {FrameDescription} frame
92
124
  * @param {number} width @param {number} height drawing buffer pixels
93
125
  * @param {number} pixelRatio
94
- * @returns {{ mask: WebGLTexture, wide: WebGLTexture, radius: number }}
126
+ * @param {boolean} smooth antialiased: soft edges and round corners
127
+ * @returns {OutlineMask}
95
128
  */
96
- outlineMask(outlines: FrameOutline[], frame: FrameDescription, width: number, height: number, pixelRatio: number): {
97
- mask: WebGLTexture;
98
- wide: WebGLTexture;
99
- radius: number;
100
- };
129
+ outlineMask(outlines: FrameOutline[], frame: FrameDescription, width: number, height: number, pixelRatio: number, smooth: boolean): OutlineMask;
101
130
  /**
102
131
  * Draw the outlines over the finished scene, into whatever is bound: the sideways-widened mask
103
132
  * widened up and down too, less the mask itself, so a ring is left round each subtree.
104
- * @param {{ mask: WebGLTexture, wide: WebGLTexture, radius: number }} outline
105
- * @param {number} height drawing buffer pixels
133
+ * @param {OutlineMask} outline
106
134
  */
107
- outlineComposite(outline: {
108
- mask: WebGLTexture;
109
- wide: WebGLTexture;
110
- radius: number;
111
- }, height: number): void;
135
+ outlineComposite(outline: OutlineMask): void;
112
136
  }
@@ -41,6 +41,26 @@ export type MeshBuffers = {
41
41
  */
42
42
  layout?: string;
43
43
  };
44
+ /**
45
+ * Per-vertex tangents along the UVs' u, with the way v runs in w (Lengyel's method): smooth where
46
+ * the normals are. Without UVs, around the mesh's up axis.
47
+ * @param {Geometry} g
48
+ * @returns {Float32Array}
49
+ */
50
+ export declare function tangentsOf(g: Geometry): Float32Array;
51
+ /**
52
+ * A map's UV transform as a 3x3 (column-major): repeat, then rotation about its centre, then offset -
53
+ * glTF's KHR_texture_transform order, turning the way three.js and glTF viewers do. Written into one
54
+ * shared array, so set it as a uniform straight away.
55
+ * @param {{ repeat: number[], offset: number[], rotation: number, center: number[] }} t
56
+ * @returns {Float32Array}
57
+ */
58
+ export declare function uvMatrix(t: {
59
+ repeat: number[];
60
+ offset: number[];
61
+ rotation: number;
62
+ center: number[];
63
+ }): Float32Array;
44
64
  export declare class Resources {
45
65
  #private;
46
66
  /** @type {WebGL2RenderingContext} */ gl: WebGL2RenderingContext;
@@ -59,17 +79,19 @@ export declare class Resources {
59
79
  * @param {Geometry} geometry
60
80
  * @param {{ instanceMatrices: Float32Array, instanceColors: Float32Array | null, version: number } | null} instances
61
81
  * @param {boolean} withNormals
82
+ * @param {boolean} [withTangents] add tangents, for brushed metal - kept once added
62
83
  * @returns {MeshBuffers}
63
84
  */
64
85
  mesh(geometry: Geometry, instances: {
65
86
  instanceMatrices: Float32Array;
66
87
  instanceColors: Float32Array | null;
67
88
  version: number;
68
- } | null, withNormals: boolean): MeshBuffers;
89
+ } | null, withNormals: boolean, withTangents?: boolean): MeshBuffers;
69
90
  /**
70
91
  * Draw a mesh's triangles - instanced or not, indexed or not - with the program in use, as far as
71
92
  * its drawn count goes. Every pass that draws meshes draws them this way.
72
93
  * @param {Geometry} geometry @param {{ instanceMatrices: Float32Array, instanceColors: Float32Array | null, version: number, count: number } | null} instances
94
+ * @param {boolean} [withTangents] for brushed metal
73
95
  * @returns {number} triangles drawn
74
96
  */
75
97
  drawTriangles(geometry: Geometry, instances: {
@@ -77,7 +99,7 @@ export declare class Resources {
77
99
  instanceColors: Float32Array | null;
78
100
  version: number;
79
101
  count: number;
80
- } | null): number;
102
+ } | null, withTangents?: boolean): number;
81
103
  /**
82
104
  * Vertex array for Lines: one instance per segment with its two endpoints (and colours),
83
105
  * expanded to a screen-space quad in the vertex shader.
@@ -6,6 +6,8 @@ export declare const ATTRIBUTES: {
6
6
  color: number;
7
7
  instanceMatrix: number;
8
8
  instanceColor: number;
9
+ tangent: number;
10
+ uv2: number;
9
11
  segmentStart: number;
10
12
  segmentEnd: number;
11
13
  segmentColorStart: number;
@@ -14,12 +16,12 @@ export declare const ATTRIBUTES: {
14
16
  };
15
17
  export declare const CLIP = "\n#ifdef CLIP\nuniform vec4 uClipPlane; // world space, unit normal: cut away where dot(n, p) + d > 0\nvoid clipAway(vec3 p) {\n if (dot(uClipPlane.xyz, p) + uClipPlane.w > 0.0) discard;\n}\n#else\nvoid clipAway(vec3 p) {}\n#endif\n";
16
18
  export declare const FOG = "\n#ifdef FOG\nuniform vec3 uFogColor; // linear\nuniform vec2 uFogRange; // linear fog: near, far\nuniform float uFogDensity; // exponential fog\nin float vFogDepth;\nvec3 fogged(vec3 c) {\n#ifdef FOG_LINEAR\n float f = clamp((vFogDepth - uFogRange.x) / max(uFogRange.y - uFogRange.x, 1e-4), 0.0, 1.0);\n#else\n float d = uFogDensity * vFogDepth;\n float f = 1.0 - exp(-d * d);\n#endif\n#ifdef FOG_ADD\n return c * (1.0 - f);\n#elif defined(FOG_MULTIPLY)\n // a filter in the fog fades to white, which leaves what is behind it as it was\n return mix(c, vec3(1.0), f);\n#else\n return mix(c, uFogColor, f);\n#endif\n}\n#else\nvec3 fogged(vec3 c) { return c; }\n#endif\n";
17
- export declare const CLIP_ONLY_FRAGMENT = "\nprecision highp float;\nin vec3 vWorldPosition;\nout vec4 fragColor;\n#ifdef ALPHA_TEST\n// a cut-out casts the shadow of what is left of it - leaves, fences, grilles - not of its rectangle\nin vec2 vUv;\nuniform float uOpacity;\nuniform float uAlphaTest;\n#ifdef MAP\nuniform sampler2D uMap;\n\n#ifdef TRIPLANAR\nin vec3 vObjectPosition;\nin vec3 vObjectNormal;\nin mat3 vObjectToWorld;\nuniform float uTriplanar; // repeats per unit of the mesh's own space\nuniform vec4 uUvTransform; // the map's repeat xy and offset zw, applied to each projection as to UVs\n\nvec2 projected(vec2 p) {\n return p * uUvTransform.xy + uUvTransform.zw;\n}\n\n// how much each axis's projection counts: mostly the one the surface faces, blended at the turns\nvec3 triplanarWeights(vec3 n) {\n vec3 w = pow(abs(n), vec3(4.0));\n return w / (w.x + w.y + w.z);\n}\n// a map seen from the three axes and blended - for meshes with no UVs, scans and CAD models\nvec4 triplanar(sampler2D s) {\n vec3 p = vObjectPosition * uTriplanar, w = triplanarWeights(normalize(vObjectNormal));\n return texture(s, projected(p.zy)) * w.x + texture(s, projected(p.xz)) * w.y + texture(s, projected(p.xy)) * w.z;\n}\n#define SAMPLE(s) triplanar(s)\n#else\n#define SAMPLE(s) texture(s, vUv)\n#endif\n\n#endif\n#endif\n\n#ifdef CLIP\nuniform vec4 uClipPlane; // world space, unit normal: cut away where dot(n, p) + d > 0\nvoid clipAway(vec3 p) {\n if (dot(uClipPlane.xyz, p) + uClipPlane.w > 0.0) discard;\n}\n#else\nvoid clipAway(vec3 p) {}\n#endif\n\nvoid main() {\n clipAway(vWorldPosition);\n#ifdef ALPHA_TEST\n float alpha = uOpacity;\n#ifdef MAP\n alpha *= SAMPLE(uMap).a;\n#endif\n if (alpha < uAlphaTest) discard;\n#endif\n fragColor = vec4(1.0);\n}\n";
19
+ export declare const CLIP_ONLY_FRAGMENT: string;
18
20
  export declare const COMMON = "\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n";
19
21
  export declare const EQUIRECT = "\nvec2 equirectUv(vec3 d) {\n return vec2(atan(d.z, d.x) * 0.15915494 + 0.5, acos(clamp(d.y, -1.0, 1.0)) * 0.31830989);\n}\nvec3 equirectDirection(vec2 uv) {\n float phi = (uv.x - 0.5) * 6.28318531, theta = uv.y * 3.14159265;\n return vec3(cos(phi) * sin(theta), cos(theta), sin(phi) * sin(theta));\n}\n";
20
22
  export declare const FULLSCREEN_VERTEX = "\nout vec2 vUv;\nvoid main() {\n vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));\n vUv = p;\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);\n}\n";
21
23
  export declare const TONE_MAP = "\nuniform float uExposure;\nuniform int uToneMapping; // 0 none, 1 aces, 2 neutral\n\nvec3 toneMap(vec3 c) {\n c *= uExposure;\n if (uToneMapping == 1) {\n // ACES filmic, Narkowicz's fit\n c = clamp((c * (2.51 * c + 0.03)) / (c * (2.43 * c + 0.59) + 0.14), 0.0, 1.0);\n } else if (uToneMapping == 2) {\n // Khronos PBR Neutral\n float x = min(c.r, min(c.g, c.b));\n float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n c -= offset;\n float peak = max(c.r, max(c.g, c.b));\n if (peak > 0.8) {\n float d = 0.2;\n float newPeak = 1.0 - d * d / (peak + d - 0.8);\n c *= newPeak / peak;\n float g = 1.0 - 1.0 / (0.15 * (peak - newPeak) + 1.0);\n c = mix(c, vec3(newPeak), g);\n }\n }\n return c;\n}\n";
22
- export declare const MESH_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 normal;\nlayout(location = 2) in vec2 uv;\nlayout(location = 3) in vec3 color;\n#ifdef INSTANCED\nlayout(location = 4) in mat4 instanceMatrix;\nlayout(location = 8) in vec3 instanceColor;\n#endif\n\nuniform mat4 uModel;\nuniform mat3 uNormalMatrix;\nuniform mat4 uViewProjection;\nuniform vec4 uUvTransform; // repeat xy, offset zw\nuniform int uHighlightIndex; // -2 none, -1 the whole node, otherwise the instance\n#ifdef FOG\nuniform mat4 uView;\nout float vFogDepth;\n#endif\n\nout vec3 vWorldPosition;\nout vec3 vNormal;\nout vec2 vUv;\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef TRIPLANAR\n// the mesh's own space, so projected maps stay on it as it moves; and how its normals turn into the world\nout vec3 vObjectPosition;\nout vec3 vObjectNormal;\nout mat3 vObjectToWorld;\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\n// Matrices times vectors only - a matrix times a matrix per vertex is four times the work, and\n// with 100,000 instances the vertex stage is most of the frame.\nvoid main() {\n vColor = vec3(1.0);\n vHighlight = uHighlightIndex == -1 ? 1.0 : 0.0;\n#ifdef INSTANCED\n vec4 local = instanceMatrix * vec4(position, 1.0);\n // the instance's normal matrix as its cofactor: the inverse transpose up to scale, from three\n // cross products, flipped back for mirroring transforms\n mat3 m = mat3(instanceMatrix);\n mat3 instanceNormal = mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1])) * sign(dot(m[0], cross(m[1], m[2])));\n vec3 n = instanceNormal * normal;\n vColor *= instanceColor;\n if (gl_InstanceID == uHighlightIndex) vHighlight = 1.0;\n#else\n vec4 local = vec4(position, 1.0);\n vec3 n = normal;\n#endif\n#ifdef VERTEX_COLORS\n vColor *= srgbToLinear(color);\n#endif\n vec4 world = uModel * local;\n vWorldPosition = world.xyz;\n vNormal = uNormalMatrix * n;\n vUv = uv * uUvTransform.xy + uUvTransform.zw;\n#ifdef TRIPLANAR\n vObjectPosition = position;\n vObjectNormal = normal;\n#ifdef INSTANCED\n vObjectToWorld = uNormalMatrix * instanceNormal;\n#else\n vObjectToWorld = uNormalMatrix;\n#endif\n#endif\n gl_Position = uViewProjection * world;\n#ifdef FOG\n vFogDepth = -(uView * world).z;\n#endif\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n}\n";
24
+ export declare const MESH_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 1) in vec3 normal;\nlayout(location = 2) in vec2 uv;\nlayout(location = 3) in vec3 color;\n#ifdef INSTANCED\nlayout(location = 4) in mat4 instanceMatrix;\nlayout(location = 8) in vec3 instanceColor;\n#endif\n#ifdef ANISOTROPY\nlayout(location = 9) in vec4 tangent;\nout vec4 vTangent;\n#endif\n#ifdef UV2\nlayout(location = 10) in vec2 uv2;\nout vec2 vUv2;\n#endif\n\nuniform mat4 uModel;\nuniform mat3 uNormalMatrix;\nuniform mat4 uViewProjection;\nuniform int uHighlightIndex; // -2 none, -1 the whole node, otherwise the instance\n#ifdef FOG\nuniform mat4 uView;\nout float vFogDepth;\n#endif\n\nout vec3 vWorldPosition;\nout vec3 vNormal;\nout vec2 vUv;\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef TRIPLANAR\n// the mesh's own space, so projected maps stay on it as it moves; and how its normals turn into the world\nout vec3 vObjectPosition;\nout vec3 vObjectNormal;\nout mat3 vObjectToWorld;\n#endif\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\n// Matrices times vectors only - a matrix times a matrix per vertex is four times the work, and\n// with 100,000 instances the vertex stage is most of the frame.\nvoid main() {\n vColor = vec3(1.0);\n vHighlight = uHighlightIndex == -1 ? 1.0 : 0.0;\n#ifdef INSTANCED\n vec4 local = instanceMatrix * vec4(position, 1.0);\n // the instance's normal matrix as its cofactor: the inverse transpose up to scale, from three\n // cross products, flipped back for mirroring transforms\n mat3 m = mat3(instanceMatrix);\n mat3 instanceNormal = mat3(cross(m[1], m[2]), cross(m[2], m[0]), cross(m[0], m[1])) * sign(dot(m[0], cross(m[1], m[2])));\n vec3 n = instanceNormal * normal;\n vColor *= instanceColor;\n if (gl_InstanceID == uHighlightIndex) vHighlight = 1.0;\n#else\n vec4 local = vec4(position, 1.0);\n vec3 n = normal;\n#endif\n#ifdef VERTEX_COLORS\n vColor *= srgbToLinear(color);\n#endif\n vec4 world = uModel * local;\n vWorldPosition = world.xyz;\n vNormal = uNormalMatrix * n;\n vUv = uv;\n#ifdef UV2\n vUv2 = uv2;\n#endif\n#ifdef ANISOTROPY\n // a direction along the surface, so it turns as the model does\n#ifdef INSTANCED\n vTangent = vec4(mat3(uModel) * (m * tangent.xyz), tangent.w);\n#else\n vTangent = vec4(mat3(uModel) * tangent.xyz, tangent.w);\n#endif\n#endif\n#ifdef TRIPLANAR\n vObjectPosition = position;\n vObjectNormal = normal;\n#ifdef INSTANCED\n vObjectToWorld = uNormalMatrix * instanceNormal;\n#else\n vObjectToWorld = uNormalMatrix;\n#endif\n#endif\n gl_Position = uViewProjection * world;\n#ifdef FOG\n vFogDepth = -(uView * world).z;\n#endif\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n}\n";
23
25
  export declare const POINTS_VERTEX = "\nlayout(location = 0) in vec3 position;\nlayout(location = 3) in vec3 color;\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform float uSize; // pixels (with the pixel ratio applied), or world units when attenuated\nuniform float uScale; // projection focal term x half the buffer height: world size -> pixels at depth 1\nuniform int uHighlightIndex; // -2 none, -1 every point, otherwise the point\n\nout vec3 vColor;\nflat out float vHighlight;\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\nvoid main() {\n vec4 viewPosition = uView * (uModel * vec4(position, 1.0));\n gl_Position = uProjection * viewPosition;\n#ifdef FOG\n vFogDepth = -viewPosition.z;\n#endif\n#ifdef SIZE_ATTENUATION\n // perspective shrinks with distance; an orthographic projection (w row 0, 0, 0, 1) doesn't\n float depth = uProjection[3][3] == 1.0 ? 1.0 : max(-viewPosition.z, 1e-4);\n gl_PointSize = uSize * uScale / depth;\n#else\n gl_PointSize = uSize;\n#endif\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(color);\n#endif\n vHighlight = uHighlightIndex == -1 || gl_VertexID == uHighlightIndex ? 1.0 : 0.0;\n#ifdef PICK\n vPickElement = uint(gl_VertexID);\n#endif\n}\n";
24
26
  export declare const LINES_VERTEX = "\nlayout(location = 0) in vec3 segmentStart;\nlayout(location = 1) in vec3 segmentEnd;\nlayout(location = 2) in vec3 segmentColorStart;\nlayout(location = 3) in vec3 segmentColorEnd;\nlayout(location = 4) in vec2 corner; // x: 0 at start, 1 at end; y: -1 or 1 across\n\nuniform mat4 uModel;\nuniform mat4 uView;\nuniform mat4 uProjection;\nuniform vec2 uResolution; // drawing buffer size in pixels\nuniform float uWidth; // pixels, already scaled by the pixel ratio\nuniform float uNear;\n\nout vec3 vColor;\n#ifdef PICK\nflat out uint vPickElement; // which instance, point or segment, for GPU picking\n#endif\n#ifdef FOG\nout float vFogDepth;\n#endif\n\n\nvec3 srgbToLinear(vec3 c) {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));\n}\nvec3 linearToSrgb(vec3 c) {\n c = clamp(c, 0.0, 1.0);\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n\nvoid main() {\n vec4 a = uView * (uModel * vec4(segmentStart, 1.0));\n vec4 b = uView * (uModel * vec4(segmentEnd, 1.0));\n // clip the segment to the near plane so lines passing behind the camera stay correct\n float clipZ = -uNear;\n if (a.z > clipZ && b.z > clipZ) {\n gl_Position = vec4(2.0, 2.0, 2.0, 1.0); // wholly behind: off screen\n return;\n }\n if (a.z > clipZ) a = mix(a, b, (clipZ - a.z) / (b.z - a.z));\n if (b.z > clipZ) b = mix(b, a, (clipZ - b.z) / (a.z - b.z));\n\n vec4 ca = uProjection * a;\n vec4 cb = uProjection * b;\n vec2 sa = ca.xy / ca.w * uResolution * 0.5;\n vec2 sb = cb.xy / cb.w * uResolution * 0.5;\n vec2 dir = sb - sa;\n float len = length(dir);\n dir = len > 0.0 ? dir / len : vec2(1.0, 0.0);\n vec2 normal = vec2(-dir.y, dir.x);\n\n vec4 clip = mix(ca, cb, corner.x);\n#ifdef FOG\n vFogDepth = -mix(a.z, b.z, corner.x);\n#endif\n // extend past each end by half the width so joins look continuous\n vec2 offset = normal * corner.y * uWidth * 0.5 + dir * (corner.x * 2.0 - 1.0) * uWidth * 0.5;\n clip.xy += offset / (uResolution * 0.5) * clip.w;\n gl_Position = clip;\n#ifdef PICK\n vPickElement = uint(gl_InstanceID);\n#endif\n\n vColor = vec3(1.0);\n#ifdef VERTEX_COLORS\n vColor = srgbToLinear(mix(segmentColorStart, segmentColorEnd, corner.x));\n#endif\n}\n";
25
27
  export type ShaderKind = 'mesh' | 'points' | 'lines';