diff --git a/examples/eslint.config.mjs b/examples/eslint.config.mjs
index e661a640307..2c28eced7fc 100644
--- a/examples/eslint.config.mjs
+++ b/examples/eslint.config.mjs
@@ -20,6 +20,7 @@ const booleanFlags = new Set([
]);
const engineTypes = new Set(['development', 'performance', 'debug']);
const creditFields = ['title', 'author', 'source', 'license'];
+const requiredCreditFields = ['title', 'author'];
const creditFieldSet = new Set(creditFields);
const splitFlag = (line) => {
@@ -134,7 +135,7 @@ const configBlockShape = {
return null;
}
- const missing = creditFields.filter(field => !credit.fields[field]);
+ const missing = requiredCreditFields.filter(field => !credit.fields[field]);
if (missing.length) {
report(line, 'missingCreditFields', { fields: missing.join(', ') });
}
diff --git a/examples/iframe/runtime.mjs b/examples/iframe/runtime.mjs
index 63b163a8c7e..2c49432ed34 100644
--- a/examples/iframe/runtime.mjs
+++ b/examples/iframe/runtime.mjs
@@ -36,6 +36,7 @@ const moduleUrls = new Map();
const moduleUrlTasks = new Map();
const configRegex = /^[ \t]*\/\/ @config[ \t]*(?:\r?\n[ \t]*\/\/[^\r\n]*)*(?:\r?\n|$)/gm;
const CREDIT_FIELDS = ['title', 'author', 'source', 'license'];
+const REQUIRED_CREDIT_FIELDS = ['title', 'author'];
const CREDIT_FIELD_SET = new Set(CREDIT_FIELDS);
const parseValue = (val) => {
@@ -52,7 +53,7 @@ const parseExampleConfig = (block, config) => {
let credit = null;
const completeCredit = () => {
- const missing = CREDIT_FIELDS.filter(field => !credit[field]);
+ const missing = REQUIRED_CREDIT_FIELDS.filter(field => !credit[field]);
if (missing.length) {
throw new Error(`Incomplete @credit: missing ${missing.join(', ')}`);
}
@@ -118,7 +119,21 @@ const parseExampleConfig = (block, config) => {
completeCredit();
}
- const text = description.join('\n').trim();
+ const paragraphs = [];
+ let current = [];
+ for (const line of description) {
+ const trimmed = line.trim();
+ if (trimmed) {
+ current.push(trimmed);
+ } else if (current.length) {
+ paragraphs.push(current.join(' '));
+ current = [];
+ }
+ }
+ if (current.length) {
+ paragraphs.push(current.join(' '));
+ }
+ const text = paragraphs.join('\n').trim();
if (text) {
config.DESCRIPTION = text;
}
diff --git a/examples/src/app/components/Example.mjs b/examples/src/app/components/Example.mjs
index f296b2e4364..341b6894e7b 100644
--- a/examples/src/app/components/Example.mjs
+++ b/examples/src/app/components/Example.mjs
@@ -493,23 +493,26 @@ class Example extends TypedComponent {
* @returns {ReactElement} rendered credit row.
*/
renderCredit(credit, index) {
- const source = URL_IN_TEXT_PATTERN.exec(credit.source);
+ const source = credit.source ? URL_IN_TEXT_PATTERN.exec(credit.source) : null;
const label = fragment(
jsx('span', { className: 'example-credit-title' }, credit.title),
' by ',
jsx('span', { className: 'example-credit-author' }, credit.author)
);
+ const children = [source ? this.renderCreditLink(source[1], label) : label];
+ if (!source && credit.source) {
+ children.push(' \u00b7 ', credit.source);
+ }
+ if (credit.license) {
+ children.push(' \u00b7 ', this.renderLicenseLink(credit.license));
+ }
return jsx(
'p',
{
className: 'example-credit',
key: index
},
- source ? this.renderCreditLink(source[1], label) : label,
- source ? null : ' \u00b7 ',
- source ? null : credit.source,
- ' \u00b7 ',
- this.renderLicenseLink(credit.license)
+ ...children
);
}
diff --git a/examples/src/app/events.js b/examples/src/app/events.js
index 07889eea59c..a7483af4f2e 100644
--- a/examples/src/app/events.js
+++ b/examples/src/app/events.js
@@ -14,8 +14,8 @@
* @typedef {object} Credit
* @property {string} title - The credit title.
* @property {string} author - The credit author.
- * @property {string} source - The credit source.
- * @property {string} license - The credit license.
+ * @property {string} [source] - The credit source (optional).
+ * @property {string} [license] - The credit license (optional).
*
* @typedef {object} StateEventDetail
* @property {Observer} observer - The PCUI observer.
diff --git a/examples/src/examples/camera/first-person.example.mjs b/examples/src/examples/camera/first-person.example.mjs
index d3471263d34..ba6b5dfe526 100644
--- a/examples/src/examples/camera/first-person.example.mjs
+++ b/examples/src/examples/camera/first-person.example.mjs
@@ -1,6 +1,12 @@
// @config
//
// `WASD` Move · `Space` Jump · `Mouse` Look
+//
+// @credit
+// title: De Dust 2 with Real Light
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/de-dust-2-with-real-light-4ce74cd95c584ce9b12b5ed9dc418db5
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
import { FirstPersonController } from 'playcanvas/scripts/esm/first-person-controller.mjs';
diff --git a/examples/src/examples/compute/edge-detect.example.mjs b/examples/src/examples/compute/edge-detect.example.mjs
index d292973a0b5..a52bd6f2ce2 100644
--- a/examples/src/examples/compute/edge-detect.example.mjs
+++ b/examples/src/examples/compute/edge-detect.example.mjs
@@ -4,6 +4,12 @@
// red.
//
// @flag WEBGL_DISABLED
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/compute/histogram.example.mjs b/examples/src/examples/compute/histogram.example.mjs
index 69f426fcb8e..009b6a81e28 100644
--- a/examples/src/examples/compute/histogram.example.mjs
+++ b/examples/src/examples/compute/histogram.example.mjs
@@ -1,5 +1,11 @@
// @config
// @flag WEBGL_DISABLED
+//
+// @credit
+// title: UXR Icosahedron
+// author: enealefons
+// source: https://sketchfab.com/3d-models/uxr-icosahedron-66c69bd0538a455197aebe81ae3a4961
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/compute/indirect-dispatch.example.mjs b/examples/src/examples/compute/indirect-dispatch.example.mjs
index cb64f91d0df..45feb1e7968 100644
--- a/examples/src/examples/compute/indirect-dispatch.example.mjs
+++ b/examples/src/examples/compute/indirect-dispatch.example.mjs
@@ -5,6 +5,12 @@
// edge (red) and smooth (blue) regions.
//
// @flag WEBGL_DISABLED
+//
+// @credit
+// title: Wide Street 02
+// author: Poly Haven
+// source: https://polyhaven.com/a/wide_street_02
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/compute/texture-gen.example.mjs b/examples/src/examples/compute/texture-gen.example.mjs
index 8cbd27c0c1e..41054927d2a 100644
--- a/examples/src/examples/compute/texture-gen.example.mjs
+++ b/examples/src/examples/compute/texture-gen.example.mjs
@@ -1,5 +1,11 @@
// @config
// @flag WEBGL_DISABLED
+//
+// @credit
+// title: UXR Icosahedron
+// author: enealefons
+// source: https://sketchfab.com/3d-models/uxr-icosahedron-66c69bd0538a455197aebe81ae3a4961
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/gaussian-splatting-xr/vr-lod.example.mjs b/examples/src/examples/gaussian-splatting-xr/vr-lod.example.mjs
index 72298f98333..0616a1b095b 100644
--- a/examples/src/examples/gaussian-splatting-xr/vr-lod.example.mjs
+++ b/examples/src/examples/gaussian-splatting-xr/vr-lod.example.mjs
@@ -1,6 +1,11 @@
// @config
// @flag HIDDEN
// @flag NO_MINISTATS
+//
+// @credit
+// title: Roman Parish
+// author: Andrii Shramko
+// source: https://www.linkedin.com/in/andrii-shramko/
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/annotations.example.mjs b/examples/src/examples/gaussian-splatting/annotations.example.mjs
index c27d7b60df6..08bac9d4292 100644
--- a/examples/src/examples/gaussian-splatting/annotations.example.mjs
+++ b/examples/src/examples/gaussian-splatting/annotations.example.mjs
@@ -2,6 +2,11 @@
//
// Interactive 3D annotations on a gaussian splat model. Click hotspots to reveal product details with
// tooltips that follow the 3D positions.
+//
+// @credit
+// title: Bicycle
+// author: Stéphane Agullo
+// source: https://www.stephane-agullo.fr/
import * as pc from 'playcanvas';
import { Annotation, AnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
diff --git a/examples/src/examples/gaussian-splatting/benchmark.example.mjs b/examples/src/examples/gaussian-splatting/benchmark.example.mjs
index 1d6fec7e086..069492cdb4b 100644
--- a/examples/src/examples/gaussian-splatting/benchmark.example.mjs
+++ b/examples/src/examples/gaussian-splatting/benchmark.example.mjs
@@ -6,6 +6,11 @@
// @flag NO_DEVICE_SELECTOR
// @flag WEBGPU_DISABLED
// @flag WEBGL_DISABLED
+//
+// @credit
+// title: Roman Parish
+// author: Andrii Shramko
+// source: https://www.linkedin.com/in/andrii-shramko/
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/gaussian-splatting/editor.example.mjs b/examples/src/examples/gaussian-splatting/editor.example.mjs
index b9c305f58e2..5d829d5b3ad 100644
--- a/examples/src/examples/gaussian-splatting/editor.example.mjs
+++ b/examples/src/examples/gaussian-splatting/editor.example.mjs
@@ -3,6 +3,12 @@
// GSplat editor with AABB selection, deletion, and cloning using GSplatProcessor.
//
// `Select button` Show selection box · `Gizmo` Move selection box · `LMB` Orbit
+//
+// @credit
+// title: SA3D_R&D_XP47
+// author: Stephane Agullo
+// source: https://superspl.at/view?id=cdcec084
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/gaussian-splatting/flipbook.example.mjs b/examples/src/examples/gaussian-splatting/flipbook.example.mjs
index 4451a8e2a36..4c100980f95 100644
--- a/examples/src/examples/gaussian-splatting/flipbook.example.mjs
+++ b/examples/src/examples/gaussian-splatting/flipbook.example.mjs
@@ -4,6 +4,16 @@
// files.
//
// @flag NO_MINISTATS
+//
+// @credit
+// title: Mirror's Edge Apartment - Interior Scene
+// author: Aurélien Martel
+// source: https://sketchfab.com/3d-models/mirrors-edge-apartment-interior-scene-9804e9f2fe284070b081c96ceaf8af96
+// license: CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)
+//
+// @credit
+// title: Basketball Player
+// author: azad_geniusxr
import * as pc from 'playcanvas';
import { GsplatFlipbook } from 'playcanvas/scripts/esm/gsplat/gsplat-flipbook.mjs';
diff --git a/examples/src/examples/gaussian-splatting/lod-instances.example.mjs b/examples/src/examples/gaussian-splatting/lod-instances.example.mjs
index d9d8a10894e..799fc82d1d1 100644
--- a/examples/src/examples/gaussian-splatting/lod-instances.example.mjs
+++ b/examples/src/examples/gaussian-splatting/lod-instances.example.mjs
@@ -2,6 +2,12 @@
//
// Demonstrates a grid of Gaussian Splat instances using the LOD system for stable performance, with a
// custom data stream storing IDs to colorize splats via a color lookup texture.
+//
+// @credit
+// title: PLAYBOT
+// author: Stephane Agullo
+// source: https://superspl.at/view?id=ee6d8bc4
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/lod-streaming-sh.example.mjs b/examples/src/examples/gaussian-splatting/lod-streaming-sh.example.mjs
index 42a203e3f84..45058d50d51 100644
--- a/examples/src/examples/gaussian-splatting/lod-streaming-sh.example.mjs
+++ b/examples/src/examples/gaussian-splatting/lod-streaming-sh.example.mjs
@@ -1,6 +1,11 @@
// @config
//
// Demonstrates LOD streaming combined with spherical harmonics for view-dependent effects.
+//
+// @credit
+// title: Skatepark
+// author: Christoph Schindelar
+// source: https://superspl.at/user?id=schindelar3d
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs b/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs
index 5fc0246281e..9bd51a45cb4 100644
--- a/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs
+++ b/examples/src/examples/gaussian-splatting/lod-streaming.example.mjs
@@ -3,6 +3,11 @@
// Demonstrates LOD streaming with radial reveal effect for progressive loading of Gaussian Splats.
//
// @flag NO_MINISTATS
+//
+// @credit
+// title: Roman Parish
+// author: Andrii Shramko
+// source: https://www.linkedin.com/in/andrii-shramko/
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/multi-splat.example.mjs b/examples/src/examples/gaussian-splatting/multi-splat.example.mjs
index cbe8fdfdcee..2affe05b87e 100644
--- a/examples/src/examples/gaussian-splatting/multi-splat.example.mjs
+++ b/examples/src/examples/gaussian-splatting/multi-splat.example.mjs
@@ -1,6 +1,12 @@
// @config
//
// Shows multiple Gaussian Splat objects in a gallery scene with custom vertex shaders.
+//
+// @credit
+// title: VR Gallery
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/vr-gallery-1e087aa25dc742e680accb15249bd6be
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/gaussian-splatting/paint.example.mjs b/examples/src/examples/gaussian-splatting/paint.example.mjs
index bd71827150d..cc46c166052 100644
--- a/examples/src/examples/gaussian-splatting/paint.example.mjs
+++ b/examples/src/examples/gaussian-splatting/paint.example.mjs
@@ -3,6 +3,12 @@
// 3D painting on gaussian splats using GSplatProcessor.
//
// `RMB` Paint · `LMB` Orbit
+//
+// @credit
+// title: SA3D_R&D_XP47
+// author: Stephane Agullo
+// source: https://superspl.at/view?id=cdcec084
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/gaussian-splatting/procedural-mesh.example.mjs b/examples/src/examples/gaussian-splatting/procedural-mesh.example.mjs
index e1d955c7397..5494c7b37e1 100644
--- a/examples/src/examples/gaussian-splatting/procedural-mesh.example.mjs
+++ b/examples/src/examples/gaussian-splatting/procedural-mesh.example.mjs
@@ -2,6 +2,12 @@
//
// Procedural mesh converted to gaussian splats. Demonstrates converting a terrain scene with animated
// clouds to splat representation.
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
import { GsplatMesh } from 'playcanvas/scripts/esm/gsplat/gsplat-mesh.mjs';
diff --git a/examples/src/examples/gaussian-splatting/procedural-shapes.example.mjs b/examples/src/examples/gaussian-splatting/procedural-shapes.example.mjs
index e8351266eb7..551dededa6a 100644
--- a/examples/src/examples/gaussian-splatting/procedural-shapes.example.mjs
+++ b/examples/src/examples/gaussian-splatting/procedural-shapes.example.mjs
@@ -1,6 +1,11 @@
// @config
//
// Procedural shapes rendered using gaussian splats. Demonstrates lines, text and image-based splats.
+//
+// @credit
+// title: Bicycle
+// author: Stéphane Agullo
+// source: https://www.stephane-agullo.fr/
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/shadows.example.mjs b/examples/src/examples/gaussian-splatting/shadows.example.mjs
index 4e8be677974..b886931246a 100644
--- a/examples/src/examples/gaussian-splatting/shadows.example.mjs
+++ b/examples/src/examples/gaussian-splatting/shadows.example.mjs
@@ -3,6 +3,12 @@
// Demonstrates shadow catching with Gaussian Splats.
//
// @flag HIDDEN
+//
+// @credit
+// title: St Peter's Square Night
+// author: Poly Haven
+// source: https://polyhaven.com/a/st_peters_square_night
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
import { ShadowCatcher } from 'playcanvas/scripts/esm/shadow-catcher.mjs';
diff --git a/examples/src/examples/gaussian-splatting/splat-portal.example.mjs b/examples/src/examples/gaussian-splatting/splat-portal.example.mjs
index 69055f19227..b9df3b56fbc 100644
--- a/examples/src/examples/gaussian-splatting/splat-portal.example.mjs
+++ b/examples/src/examples/gaussian-splatting/splat-portal.example.mjs
@@ -4,6 +4,22 @@
// through it.
//
// @flag NO_MINISTATS
+//
+// @credit
+// title: Portal Frame
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/portal-frame-da34b37a224e4e49b307c0b17a50af2c
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Roman Parish
+// author: Andrii Shramko
+// source: https://www.linkedin.com/in/andrii-shramko/
+//
+// @credit
+// title: Skatepark
+// author: Christoph Schindelar
+// source: https://superspl.at/user?id=schindelar3d
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/viewer.example.mjs b/examples/src/examples/gaussian-splatting/viewer.example.mjs
index 5f65b9c3ba1..ddc18f6900e 100644
--- a/examples/src/examples/gaussian-splatting/viewer.example.mjs
+++ b/examples/src/examples/gaussian-splatting/viewer.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Wide Street 02
+// author: Poly Haven
+// source: https://polyhaven.com/a/wide_street_02
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/gaussian-splatting/weather.example.mjs b/examples/src/examples/gaussian-splatting/weather.example.mjs
index 1a7ba94d373..b25b4175787 100644
--- a/examples/src/examples/gaussian-splatting/weather.example.mjs
+++ b/examples/src/examples/gaussian-splatting/weather.example.mjs
@@ -4,6 +4,11 @@
// Particles follow the camera using a deterministic 3D grid with hash-based positioning and animation.
//
// @flag NO_MINISTATS
+//
+// @credit
+// title: Roman Parish
+// author: Andrii Shramko
+// source: https://www.linkedin.com/in/andrii-shramko/
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/gaussian-splatting/world.example.mjs b/examples/src/examples/gaussian-splatting/world.example.mjs
index 1bdd0702ff5..93fb1b8128a 100644
--- a/examples/src/examples/gaussian-splatting/world.example.mjs
+++ b/examples/src/examples/gaussian-splatting/world.example.mjs
@@ -1,6 +1,11 @@
// @config
//
// Shows a large world scene with LOD streaming and additional moving splats.
+//
+// @credit
+// title: Skatepark
+// author: Christoph Schindelar
+// source: https://superspl.at/user?id=schindelar3d
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/graphics/ambient-occlusion.example.mjs b/examples/src/examples/graphics/ambient-occlusion.example.mjs
index 397bbd44dfd..368ee0459cf 100644
--- a/examples/src/examples/graphics/ambient-occlusion.example.mjs
+++ b/examples/src/examples/graphics/ambient-occlusion.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Laboratory
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/laboratory-e860e49837c044478db650868866a448
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/asset-viewer.example.mjs b/examples/src/examples/graphics/asset-viewer.example.mjs
index 2fc1256c5e5..4dc77093402 100644
--- a/examples/src/examples/graphics/asset-viewer.example.mjs
+++ b/examples/src/examples/graphics/asset-viewer.example.mjs
@@ -1,3 +1,29 @@
+// @config
+//
+// @credit
+// title: Iridescent Dish with Olives
+// author: Wayfair LLC
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/IridescentDishWithOlives
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Real-time Refraction Demo: Mosquito in Amber
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/real-time-refraction-demo-mosquito-in-amber-37233d6ed84844fea1ebe88069ea58d1
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Sheen Chair
+// author: Wayfair LLC
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/SheenChair
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Stained Glass Lamp
+// author: Wayfair LLC
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/StainedGlassLamp
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/custom-compose-shader.example.mjs b/examples/src/examples/graphics/custom-compose-shader.example.mjs
index a7925c6ff9d..85a87454336 100644
--- a/examples/src/examples/graphics/custom-compose-shader.example.mjs
+++ b/examples/src/examples/graphics/custom-compose-shader.example.mjs
@@ -3,6 +3,18 @@
// This example shows how to customize the final compose pass by injecting a simple pixelation
// post-effect. Useful if no additional render passes are needed. Changes are applied globally to all
// CameraFrames.
+//
+// @credit
+// title: Mirror's Edge Apartment - Interior Scene
+// author: Aurélien Martel
+// source: https://sketchfab.com/3d-models/mirrors-edge-apartment-interior-scene-9804e9f2fe284070b081c96ceaf8af96
+// license: CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)
+//
+// @credit
+// title: Love neon sign 02
+// author: daysena
+// source: https://sketchfab.com/3d-models/love-neon-sign-02-9add8bfcb25943d0aae87e0af07c8e4d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/graphics/depth-of-field.example.mjs b/examples/src/examples/graphics/depth-of-field.example.mjs
index 0103163ef90..6b8280c7bdf 100644
--- a/examples/src/examples/graphics/depth-of-field.example.mjs
+++ b/examples/src/examples/graphics/depth-of-field.example.mjs
@@ -1,3 +1,23 @@
+// @config
+//
+// @credit
+// title: Mirror's Edge Apartment - Interior Scene
+// author: Aurélien Martel
+// source: https://sketchfab.com/3d-models/mirrors-edge-apartment-interior-scene-9804e9f2fe284070b081c96ceaf8af96
+// license: CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)
+//
+// @credit
+// title: Love neon sign 02
+// author: daysena
+// source: https://sketchfab.com/3d-models/love-neon-sign-02-9add8bfcb25943d0aae87e0af07c8e4d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Egyptian Cat Statue
+// author: Ankledot
+// source: https://sketchfab.com/3d-models/egyptian-cat-statue-02b0456362f9442da46d39fb34b3ee5b
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/dithered-transparency.example.mjs b/examples/src/examples/graphics/dithered-transparency.example.mjs
index e2dc3b972cf..198569502b9 100644
--- a/examples/src/examples/graphics/dithered-transparency.example.mjs
+++ b/examples/src/examples/graphics/dithered-transparency.example.mjs
@@ -1,11 +1,15 @@
// @config
//
-//
-// Independent strengths for alpha blending and opacity dithering. Both tables have alpha
-// blending and dither enabled. Left: alphaDither untouched — opacity drives both
-// blend and dither (legacy behavior). Right: alphaDither driven by its slider —
-// opacity drives only blend; Alpha Dither drives only dither.
-//
+// Independent strengths for alpha blending and opacity dithering. Both tables have alpha blending
+// and dither enabled. Left: `alphaDither` untouched — opacity drives both blend and dither
+// (legacy behavior). Right: `alphaDither` driven by its slider — opacity drives only blend;
+// **Alpha Dither** drives only dither.
+//
+// @credit
+// title: Low-poly Glass Table
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/low-poly-glass-table-6acac6d9201e448b92dff859b6f63aad
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/graphics/hdr.example.mjs b/examples/src/examples/graphics/hdr.example.mjs
index c46e9b04dca..f4562660ac0 100644
--- a/examples/src/examples/graphics/hdr.example.mjs
+++ b/examples/src/examples/graphics/hdr.example.mjs
@@ -1,3 +1,17 @@
+// @config
+//
+// @credit
+// title: Mirror's Edge Apartment - Interior Scene
+// author: Aurélien Martel
+// source: https://sketchfab.com/3d-models/mirrors-edge-apartment-interior-scene-9804e9f2fe284070b081c96ceaf8af96
+// license: CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)
+//
+// @credit
+// title: Love neon sign 02
+// author: daysena
+// source: https://sketchfab.com/3d-models/love-neon-sign-02-9add8bfcb25943d0aae87e0af07c8e4d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/instancing-gooch.example.mjs b/examples/src/examples/graphics/instancing-gooch.example.mjs
index 9ab40f9c2b1..0be98feac10 100644
--- a/examples/src/examples/graphics/instancing-gooch.example.mjs
+++ b/examples/src/examples/graphics/instancing-gooch.example.mjs
@@ -2,6 +2,12 @@
//
// This example demonstrates how a custom shader can be used to render instanced geometry, but also
// skinned, morphed and static geometry. A simple Gooch shading shader is used.
+//
+// @credit
+// title: Low-poly Tree with Twisting Branches
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/low-poly-tree-with-twisting-branches-4e2589134f2442bcbdab51c1f306cd58
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/graphics/light-physical-units.example.mjs b/examples/src/examples/graphics/light-physical-units.example.mjs
index 67d86f67c76..71cbc183d0d 100644
--- a/examples/src/examples/graphics/light-physical-units.example.mjs
+++ b/examples/src/examples/graphics/light-physical-units.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Sheen Chair
+// author: Wayfair LLC
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/SheenChair
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType, win } from 'examples/context';
diff --git a/examples/src/examples/graphics/lights-baked-a-o.example.mjs b/examples/src/examples/graphics/lights-baked-a-o.example.mjs
index 577a0e6a10b..486b427b0f8 100644
--- a/examples/src/examples/graphics/lights-baked-a-o.example.mjs
+++ b/examples/src/examples/graphics/lights-baked-a-o.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: House Scene
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/house-scene-52772448c62348e0a4951b51758d5587
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/mesh-morph-many.example.mjs b/examples/src/examples/graphics/mesh-morph-many.example.mjs
index 8f3d27d72e1..a991b2bc599 100644
--- a/examples/src/examples/graphics/mesh-morph-many.example.mjs
+++ b/examples/src/examples/graphics/mesh-morph-many.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: MorphStressTest
+// author: Ed Mackey
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/MorphStressTest/README.md
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/multi-draw.example.mjs b/examples/src/examples/graphics/multi-draw.example.mjs
index 3cfda88a083..e8bbbc64140 100644
--- a/examples/src/examples/graphics/multi-draw.example.mjs
+++ b/examples/src/examples/graphics/multi-draw.example.mjs
@@ -2,6 +2,12 @@
//
// Terrain rendering using a single draw call built from a grid of displaced planes. Each patch is a
// sub-draw and can be culled (hidden) dynamically.
+//
+// @credit
+// title: Canyon and River Height Map
+// author: Motion Forge Pictures
+// source: https://www.motionforgepictures.com/height-maps/
+// license: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/graphics/multi-render-targets.example.mjs b/examples/src/examples/graphics/multi-render-targets.example.mjs
index aa23a2428e7..a969d4e90c5 100644
--- a/examples/src/examples/graphics/multi-render-targets.example.mjs
+++ b/examples/src/examples/graphics/multi-render-targets.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/multi-view.example.mjs b/examples/src/examples/graphics/multi-view.example.mjs
index 4d8fa6ec50e..1e0ffe95663 100644
--- a/examples/src/examples/graphics/multi-view.example.mjs
+++ b/examples/src/examples/graphics/multi-view.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/outlines-colored.example.mjs b/examples/src/examples/graphics/outlines-colored.example.mjs
index ee9a30ae41c..47d94aff2a6 100644
--- a/examples/src/examples/graphics/outlines-colored.example.mjs
+++ b/examples/src/examples/graphics/outlines-colored.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Laboratory
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/laboratory-e860e49837c044478db650868866a448
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/portal.example.mjs b/examples/src/examples/graphics/portal.example.mjs
index bc607858c3d..c9346313741 100644
--- a/examples/src/examples/graphics/portal.example.mjs
+++ b/examples/src/examples/graphics/portal.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Portal Frame
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/portal-frame-da34b37a224e4e49b307c0b17a50af2c
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/post-effects.example.mjs b/examples/src/examples/graphics/post-effects.example.mjs
index 703144f5c90..c63045c9cb5 100644
--- a/examples/src/examples/graphics/post-effects.example.mjs
+++ b/examples/src/examples/graphics/post-effects.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/post-processing.example.mjs b/examples/src/examples/graphics/post-processing.example.mjs
index 36eddc08d2c..96c9c5da047 100644
--- a/examples/src/examples/graphics/post-processing.example.mjs
+++ b/examples/src/examples/graphics/post-processing.example.mjs
@@ -1,3 +1,17 @@
+// @config
+//
+// @credit
+// title: Real-time Refraction Demo: Mosquito in Amber
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/real-time-refraction-demo-mosquito-in-amber-37233d6ed84844fea1ebe88069ea58d1
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Scifi Platform Stage Scene (Baked)
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/scifi-platform-stage-scene-baked-64adb59a716d43e5a8705ff6fe86c0ce
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/render-pass.example.mjs b/examples/src/examples/graphics/render-pass.example.mjs
index 373f40244d5..0b83798702e 100644
--- a/examples/src/examples/graphics/render-pass.example.mjs
+++ b/examples/src/examples/graphics/render-pass.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/shadow-cascades.example.mjs b/examples/src/examples/graphics/shadow-cascades.example.mjs
index 4a7d99a41e3..2ae8a559ce1 100644
--- a/examples/src/examples/graphics/shadow-cascades.example.mjs
+++ b/examples/src/examples/graphics/shadow-cascades.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/shadow-catcher.example.mjs b/examples/src/examples/graphics/shadow-catcher.example.mjs
index d52a7a162fb..30e494c8f14 100644
--- a/examples/src/examples/graphics/shadow-catcher.example.mjs
+++ b/examples/src/examples/graphics/shadow-catcher.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: St Peter's Square Night
+// author: Poly Haven
+// source: https://polyhaven.com/a/st_peters_square_night
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { ShadowCatcher } from 'playcanvas/scripts/esm/shadow-catcher.mjs';
diff --git a/examples/src/examples/graphics/shadow-soft.example.mjs b/examples/src/examples/graphics/shadow-soft.example.mjs
index e7327003663..a4e0d63fe85 100644
--- a/examples/src/examples/graphics/shadow-soft.example.mjs
+++ b/examples/src/examples/graphics/shadow-soft.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/sky.example.mjs b/examples/src/examples/graphics/sky.example.mjs
index 7bbaaa5bc4f..67c25a82d50 100644
--- a/examples/src/examples/graphics/sky.example.mjs
+++ b/examples/src/examples/graphics/sky.example.mjs
@@ -1,3 +1,17 @@
+// @config
+//
+// @credit
+// title: Wide Street 02
+// author: Poly Haven
+// source: https://polyhaven.com/a/wide_street_02
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Small Empty Room 2
+// author: Poly Haven
+// source: https://polyhaven.com/a/small_empty_room_2
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/graphics/taa.example.mjs b/examples/src/examples/graphics/taa.example.mjs
index fae3420e4d0..eb29f5018cf 100644
--- a/examples/src/examples/graphics/taa.example.mjs
+++ b/examples/src/examples/graphics/taa.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: House 03 PBR
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/house-03-pbr-c56521b89188460a99235dec8bcd0ed3
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/loaders/gltf-export.example.mjs b/examples/src/examples/loaders/gltf-export.example.mjs
index 3e9da2cf3ed..f99119b093a 100644
--- a/examples/src/examples/loaders/gltf-export.example.mjs
+++ b/examples/src/examples/loaders/gltf-export.example.mjs
@@ -1,3 +1,17 @@
+// @config
+//
+// @credit
+// title: bench_wooden_01
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/bench-wooden-01-1400c9340d5049589deb43601462ac55
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/loaders/usdz-export.example.mjs b/examples/src/examples/loaders/usdz-export.example.mjs
index 9dec95b562e..0da7c618fd2 100644
--- a/examples/src/examples/loaders/usdz-export.example.mjs
+++ b/examples/src/examples/loaders/usdz-export.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: bench_wooden_01
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/bench-wooden-01-1400c9340d5049589deb43601462ac55
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/materials/normals-and-tangents.example.mjs b/examples/src/examples/materials/normals-and-tangents.example.mjs
index 86a7a529f9c..ac6fb8b68fe 100644
--- a/examples/src/examples/materials/normals-and-tangents.example.mjs
+++ b/examples/src/examples/materials/normals-and-tangents.example.mjs
@@ -1,5 +1,11 @@
// @config
// @flag HIDDEN
+//
+// @credit
+// title: NormalTangentTest
+// author: Ed Mackey
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/blob/main/2.0/NormalTangentTest/README.md
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/misc/annotations.example.mjs b/examples/src/examples/misc/annotations.example.mjs
index f6fafe8fba4..572def1f3fb 100644
--- a/examples/src/examples/misc/annotations.example.mjs
+++ b/examples/src/examples/misc/annotations.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Mitsubishi F-2 - Fighter Jet - Free
+// author: bohmerang
+// source: https://sketchfab.com/3d-models/mitsubishi-f-2-fighter-jet-free-d3d7244554974f499b106e6c11fe3aaf
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { Annotation, AnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
diff --git a/examples/src/examples/shaders/cloud-shadows.example.mjs b/examples/src/examples/shaders/cloud-shadows.example.mjs
index d1b11708a00..2672bb32da2 100644
--- a/examples/src/examples/shaders/cloud-shadows.example.mjs
+++ b/examples/src/examples/shaders/cloud-shadows.example.mjs
@@ -2,6 +2,12 @@
//
// This example demonstrates scrolling cloud shadows using a shader chunk
// override on {accent:StandardMaterial}.
+//
+// @credit
+// title: Low-poly Tree with Twisting Branches
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/low-poly-tree-with-twisting-branches-4e2589134f2442bcbdab51c1f306cd58
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/shaders/ground-fog.example.mjs b/examples/src/examples/shaders/ground-fog.example.mjs
index abe6be10989..89891d66733 100644
--- a/examples/src/examples/shaders/ground-fog.example.mjs
+++ b/examples/src/examples/shaders/ground-fog.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { data, deviceType } from 'examples/context';
diff --git a/examples/src/examples/shaders/shader-hatch.example.mjs b/examples/src/examples/shaders/shader-hatch.example.mjs
index 99e3b7ac0d2..108bcd5cffb 100644
--- a/examples/src/examples/shaders/shader-hatch.example.mjs
+++ b/examples/src/examples/shaders/shader-hatch.example.mjs
@@ -1,3 +1,23 @@
+// @config
+//
+// @credit
+// title: Chess Board
+// author: Idmental
+// source: https://sketchfab.com/3d-models/chess-board-901eeeca884f4622ac37b7e8f7cb82c3
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: MorphStressTest
+// author: Ed Mackey
+// source: https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/MorphStressTest/README.md
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
+//
+// @credit
+// title: Cross-hatching textures
+// author: Jaume Sanchez (spite)
+// source: https://github.com/spite/cross-hatching
+// license: MIT
+
import * as pc from 'playcanvas';
import { createHatchMaterial } from 'examples/assets/scripts/misc/hatch-material.mjs';
diff --git a/examples/src/examples/shaders/trees.example.mjs b/examples/src/examples/shaders/trees.example.mjs
index a578f2e0a9a..3b6f9c3554e 100644
--- a/examples/src/examples/shaders/trees.example.mjs
+++ b/examples/src/examples/shaders/trees.example.mjs
@@ -1,6 +1,12 @@
// @config
//
// This example shows how to override shader chunks of {accent:StandardMaterial}.
+//
+// @credit
+// title: Low-poly Tree with Twisting Branches
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/low-poly-tree-with-twisting-branches-4e2589134f2442bcbdab51c1f306cd58
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/test/contact-hardening-shadows.example.mjs b/examples/src/examples/test/contact-hardening-shadows.example.mjs
index e338e1c04a3..3b4d3f38cb9 100644
--- a/examples/src/examples/test/contact-hardening-shadows.example.mjs
+++ b/examples/src/examples/test/contact-hardening-shadows.example.mjs
@@ -1,6 +1,12 @@
// @config
// @flag HIDDEN
// @flag WEBGPU_DISABLED
+//
+// @credit
+// title: Black Honey Robotic Arm
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/black-honey-robotic-arm-c50671f2a8e74de2a2e687103fdc93ab
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/test/global-shader-properties.example.mjs b/examples/src/examples/test/global-shader-properties.example.mjs
index b48148ddbfe..435be987dda 100644
--- a/examples/src/examples/test/global-shader-properties.example.mjs
+++ b/examples/src/examples/test/global-shader-properties.example.mjs
@@ -1,5 +1,11 @@
// @config
// @flag HIDDEN
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/test/specular.example.mjs b/examples/src/examples/test/specular.example.mjs
index 5724a07b1ed..7af7f931069 100644
--- a/examples/src/examples/test/specular.example.mjs
+++ b/examples/src/examples/test/specular.example.mjs
@@ -5,6 +5,12 @@
// matching factor), and the last sphere on row 7 should look like a mirror ball.
//
// @flag HIDDEN
+//
+// @credit
+// title: Specular Test
+// author: Ed Mackey / Analytical Graphics, Inc.
+// source: https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/SpecularTest
+// license: CC BY 4.0 (http://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/test/xr-views.example.mjs b/examples/src/examples/test/xr-views.example.mjs
index 258f37a1ca8..d5a7352ab2b 100644
--- a/examples/src/examples/test/xr-views.example.mjs
+++ b/examples/src/examples/test/xr-views.example.mjs
@@ -1,5 +1,11 @@
// @config
// @flag HIDDEN
+//
+// @credit
+// title: Terrain Low Poly
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/terrain-low-poly-248b21331315466e98d20c441935d99d
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
import * as pc from 'playcanvas';
diff --git a/examples/src/examples/xr/vr-basic.example.mjs b/examples/src/examples/xr/vr-basic.example.mjs
index 21a32b78886..d0b3b255055 100644
--- a/examples/src/examples/xr/vr-basic.example.mjs
+++ b/examples/src/examples/xr/vr-basic.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: XR VR Gallery Space
+// author: Josh Johanson
+// source: https://sketchfab.com/3d-models/xr-vr-gallery-space-873a1808080e47d2a804f3c991e33b4f
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { deviceType } from 'examples/context';
diff --git a/examples/src/examples/xr/vr-test-bed.example.mjs b/examples/src/examples/xr/vr-test-bed.example.mjs
index 533de6a6c7e..1734e571c8b 100644
--- a/examples/src/examples/xr/vr-test-bed.example.mjs
+++ b/examples/src/examples/xr/vr-test-bed.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: XR VR Gallery Space
+// author: Josh Johanson
+// source: https://sketchfab.com/3d-models/xr-vr-gallery-space-873a1808080e47d2a804f3c991e33b4f
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { XrMenu } from 'playcanvas/scripts/esm/xr-menu.mjs';
diff --git a/examples/src/examples/xr/xr-menu.example.mjs b/examples/src/examples/xr/xr-menu.example.mjs
index 95ceb835f6a..89015d71e53 100644
--- a/examples/src/examples/xr/xr-menu.example.mjs
+++ b/examples/src/examples/xr/xr-menu.example.mjs
@@ -1,3 +1,11 @@
+// @config
+//
+// @credit
+// title: VR Gallery
+// author: Sketchfab
+// source: https://sketchfab.com/3d-models/vr-gallery-1e087aa25dc742e680accb15249bd6be
+// license: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
+
import * as pc from 'playcanvas';
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
import { XrControllers } from 'playcanvas/scripts/esm/xr-controllers.mjs';
diff --git a/examples/utils/example-source.mjs b/examples/utils/example-source.mjs
index e60573b838f..0d788c336f9 100644
--- a/examples/utils/example-source.mjs
+++ b/examples/utils/example-source.mjs
@@ -6,6 +6,7 @@ const regexPatterns = [
];
const configRegex = /^[ \t]*\/\/ @config[ \t]*(?:\r?\n[ \t]*\/\/[^\r\n]*)*(?:\r?\n|$)/gm;
const CREDIT_FIELDS = ['title', 'author', 'source', 'license'];
+const REQUIRED_CREDIT_FIELDS = ['title', 'author'];
const CREDIT_FIELD_SET = new Set(CREDIT_FIELDS);
const parseValue = (val) => {
@@ -22,7 +23,7 @@ const parseExampleConfig = (block, config) => {
let credit = null;
const completeCredit = () => {
- const missing = CREDIT_FIELDS.filter(field => !credit[field]);
+ const missing = REQUIRED_CREDIT_FIELDS.filter(field => !credit[field]);
if (missing.length) {
throw new Error(`Incomplete @credit: missing ${missing.join(', ')}`);
}
@@ -88,7 +89,21 @@ const parseExampleConfig = (block, config) => {
completeCredit();
}
- const text = description.join('\n').trim();
+ const paragraphs = [];
+ let current = [];
+ for (const line of description) {
+ const trimmed = line.trim();
+ if (trimmed) {
+ current.push(trimmed);
+ } else if (current.length) {
+ paragraphs.push(current.join(' '));
+ current = [];
+ }
+ }
+ if (current.length) {
+ paragraphs.push(current.join(' '));
+ }
+ const text = paragraphs.join('\n').trim();
if (text) {
config.DESCRIPTION = text;
}
@@ -125,7 +140,7 @@ export const stripConfig = (source) => {
/**
* @typedef {object} ExampleConfig
* @property {string} [DESCRIPTION] - The example description.
- * @property {{ title: string, author: string, source: string, license: string }[]} [CREDITS] - Scene credits.
+ * @property {{ title: string, author: string, source?: string, license?: string }[]} [CREDITS] - Scene credits.
* @property {boolean} [HIDDEN] - The example is hidden from the sidebar list in production builds (`npm run build`). It is still built and reachable via its URL. In development (`npm run develop`) it is still shown in the sidebar.
* @property {'development' | 'performance' | 'debug'} [ENGINE] - The engine type.
* @property {boolean} [NO_DEVICE_SELECTOR] - No device selector.