Loading index.html +16 −14 Changes for index.html: 16 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -19,23 +19,25 @@ <label for="select-dataset">{{t('controls.selected')}}</label> <span>{{t('colon')}}</span> <select name="dataset" id="select-dataset" class="btn-dark" :disabled="datasetsDisabled" :value="dataset" @change="onDatasetSelect($event)"> :value="dataset" @change="app.onDatasetSelect($event)"> <optgroup label="" :label="t('controls.hosted')" class="builtins"> <option v-for="ds in datasets.server" :value="ds.id">{{getLocString(ds.name, locale)}}</option> <option v-for="ds in datasets.server" :value="ds.id">{{loc(ds.name, locale)}}</option> </optgroup> <optgroup label="" :label="t('controls.inmemory')" class="inmemory"> <option v-for="ds in datasets.memory" :value="ds.id">{{getLocString(ds.name, locale)}}</option> <option v-for="ds in datasets.memory" :value="ds.id">{{loc(ds.name, locale)}}</option> <option class="action" value="create">{{t('controls.createinmemory')}}</option> </optgroup> </select> <button id="button-delete-dataset" class="hidden btn-dark">{{t('controls.deleteinmemory')}} <button id="button-delete-dataset" class="btn-dark" v-show="dataset.startsWith('memory:')" @click="app.deleteMemoryDataset(dataset)"> {{t('controls.deleteinmemory')}} </button> </div> <div> <button class="btn-dark btn-icon hidden" id="button-fullscreen" aria-label="Fullscreen" title="Fullscreen" data-icon="fullscreen"></button> <button class="btn-dark btn-icon" aria-label="Fullscreen" title="Fullscreen" data-icon="fullscreen" v-show="requestFullscreen" @click="requestFullscreen"></button> <select aria-label="Language" class="btn-dark" name="lang" :value="locale" @change="setLocale($event.target.value)"> :value="locale" @change="app.setLocale($event.target.value)"> <option v-for="lang in locales" :value="lang.id">{{lang.name}}</option> </select> <button class="btn-dark" id="button-help"> Loading Loading @@ -101,14 +103,14 @@ <div class="description-container"> <div v-if="description"> <h3>{{t('controls.description')}}</h3> <div class="content">{{ description && getLocString(description, locale) }}</div> <div class="content">{{ description && loc(description, locale) }}</div> </div> <pre v-if="output"><code>{{ output }}</code></pre> </div> <div class="lightball-container"> <canvas id="ivoire-lightball"></canvas> <div class="reset-button-container"> <button class="btn-dark btn-icon" id="button-reset-lightball" <button class="btn-dark btn-icon" @click="app.resetLightball()" :title="t('controls.resetlightball')" data-icon="reset" v-if="phase==='render'"></button> </div> </div> Loading @@ -129,12 +131,12 @@ </main> </div> <dialog id="message" class="drop-shadow"> <dialog id="message" class="drop-shadow" @click="app.onDialogClick"> <h1></h1> <p></p> <button class="btn-dark" type="reset" autofocus>{{t('ok')}}</button> <button class="btn-dark" type="reset" autofocus @click="$el.parent.close()">{{t('ok')}}</button> </dialog> <dialog id="create-inmemory" class="drop-shadow"> <dialog id="create-inmemory" class="drop-shadow" @click="app.onDialogClick"> <h1>{{t('memcreate.title')}}</h1> <form action="#"> <label for="upload-dataset-name" autofocus> Loading @@ -154,7 +156,7 @@ <button class="btn" type="reset">{{t('cancel')}}</button> <button class="btn" type="submit">{{t('memcreate.create')}}</button> </dialog> <dialog id="export-success"> <dialog id="export-success" @click="app.onDialogClick"> <h3>{{t('export.success')}}</h3> <img src="" alt="Exported image"> <div> Loading @@ -163,7 +165,7 @@ </div> <a href="" id="export-download-anchor" class="hidden"></a> </dialog> <dialog id="documentation"> <dialog id="documentation" @click="app.onDialogClick"> <button type="reset" class="btn-close btn-icon" data-icon="close"></button> <div class="logos"> <a target="_blank" href="https://www.greyc.fr"> Loading src/app.ts +55 −65 Changes for src/app.ts: 55 added lines, 65 removed lines. Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ import { WebGLCanvasRenderingContext, } from './gl-program.ts'; import {ControlUpdateEvent, getStateDefaults, LIGHT_ANGLE_DEFAULTS, State} from './control.ts'; import {catchError, exportValues, getLightSourceVec, matToString, roundTo} from './utils.ts'; import {catchError, exportValues, getLightSourceVec, matToString, roundTo, VERBOSE} from './utils.ts'; import {DownloadManager} from './download.ts'; import {Dataset, MemoryDataset, SingleDataset, TiledDataset} from './dataset.ts'; import {ExportWorkerData, ExportWorkerResult} from './workers/export.ts'; Loading Loading @@ -76,7 +76,6 @@ export class App { this.initControls(); this.rx.phase = 'load-datasets'; if (!import.meta.env.VITE_DATASET_FILES) throw new Error('Missing VITE_DATASET_FILES from environment'); let selected: string | undefined = undefined; if (history.state) { selected = (history.state as HistoryState).dataset || undefined; Loading @@ -84,6 +83,7 @@ export class App { selected = window.location.hash.substring(1); } if (!import.meta.env.VITE_DATASET_FILES) throw new Error('Missing VITE_DATASET_FILES from environment'); const datasetUrls = (import.meta.env.VITE_DATASET_FILES as string) .split(',') .map((u) => new URL(u, document.location.href)); Loading Loading @@ -147,21 +147,30 @@ export class App { } private get DEFAULT_RX_STATE() { // noinspection JSUnusedGlobalSymbols // Give access to those methods const fn = Object.fromEntries([ this.getLocString, this.setLocale, this.deleteMemoryDataset, this.onDatasetSelect, this.onDialogClick, this.resetLightball, this.resetControls, ].map((m) => [m.name, m.bind(this)] as const)); return { fn, phase: 'initial' as RunPhase, datasets: { server: new Array<{ id: string; name: LocString }>(), memory: new Array<{ id: string; name: LocString }>(), }, datasetsDisabled: false, onDatasetSelect: this.onDatasetSelect.bind(this), dataset: '', requestFullscreen: null as (() => void) | null, t: (k: string) => this.i18n.t(k), loc: this.getLocString.bind(this), locale: this.i18n.locale, locales: App.LANGS.map((id) => ({ id, name: this.i18n.translations[id].lang as string })), setLocale: (locale: string) => localStorage.setItem('lang', (this.i18n.locale = locale)), getLocString: this.getLocString.bind(this), description: null as LocString | null, output: null as string | null, }; Loading @@ -171,11 +180,16 @@ export class App { let text = typeof value === 'string' ? value : value[locale] || value[this.i18n.defaultLocale]!; if (!text) { console.error('Missing locale'); return "[Error: missing locale]"; return '[Error: missing locale]'; } return text; } public setLocale(locale: string) { this.i18n.locale = locale; localStorage.setItem('lang', locale); } get datasets(): Dataset[] { return new Array<Dataset>().concat(...Object.values(this._datasets)); } Loading @@ -194,15 +208,15 @@ export class App { private onDatasetSelect(ev: InputEvent) { const newValue = (ev.target as HTMLSelectElement).value; if (newValue === 'create') { if (this.rx.dataset === 'create') return; let dia = document.querySelector('dialog#create-inmemory')! as HTMLDialogElement; dia.returnValue = ''; dia.showModal(); this.setDataset(""); this.rx.dataset = "create"; this.setDataset(''); this.rx.dataset = 'create'; this.rx.phase = 'create-inmemory'; this.requestRender(); } else this.setDataset(newValue); } else this.setDataset(newValue); } private setDataset(dsId: string) { Loading Loading @@ -272,9 +286,6 @@ export class App { } this._source = value; if (value && 'dataset' in value) { document .querySelector('#button-delete-dataset')! .classList.toggle('hidden', value.dataset.type !== 'memory'); this.state.viewport.updateCanvasMat(this.gls.relight.canvas, value.totalSize, true); this.rx.phase = 'render'; this.requestRender(); Loading Loading @@ -379,7 +390,7 @@ export class App { this.programs.relight.clearCanvas(); } if (import.meta.env.DEV && import.meta.env.VITE_VERBOSE === '1') this.rx.output = output; if (VERBOSE) this.rx.output = output; } private async updateRenderArea(now: boolean) { Loading Loading @@ -465,21 +476,16 @@ export class App { } private deleteMemoryDataset(id: string) { let dsId = this._datasets.memory.findIndex((d) => d.id === id); if (dsId === -1) throw new Error(`Unknown dataset id: ${id}`); this._datasets.memory.splice(dsId, 1)[0].delete(); if (!id.startsWith('memory:')) return console.error(`Non-memory dataset id: ${id}`); let idPart = id.substring('memory:'.length), index = this._datasets.memory.findIndex((d) => d.id === idPart); if (index === -1) return console.error(`Unknown memory dataset id: ${idPart}`); console.debug(`Deleting memory dataset ${idPart}`); this._datasets.memory.splice(index, 1)[0].delete(); this.updateDatasets(); } private initControls(): void { const sheet = document.styleSheets[0]; for (const phase of RUN_PHASES) { sheet.insertRule( `.controls:not([data-phase=${phase}]) *[data-phase=${phase}] { display: none !important; }`, sheet.cssRules.length, ); } for (let ctrl of Object.values(this.state.controls)) { ctrl.addEventListener(ControlUpdateEvent.type, () => this.requestRender()); ctrl.reset(); Loading @@ -495,15 +501,6 @@ export class App { new ResizeObserver(this.onResize.bind(this)).observe(this.gls.relight.canvas); const delMemBtn = document.querySelector('button#button-delete-dataset')!; delMemBtn.addEventListener( 'click', () => this.currentDataset && this.deleteMemoryDataset(this.currentDataset.id), ); const msgDialog = document.querySelector('dialog#message') as HTMLDialogElement; msgDialog.querySelector('button[type=reset]')!.addEventListener('click', () => msgDialog.close()); const createDialog = document.querySelector('dialog#create-inmemory') as HTMLDialogElement; createDialog.querySelector('button[type=reset]')!.addEventListener('click', () => createDialog.close()); createDialog.querySelector('button[type=submit]')!.addEventListener('click', (ev) => { Loading Loading @@ -572,21 +569,6 @@ export class App { document.querySelector('#button-help')!.addEventListener('click', () => docDialog.showModal()); docDialog.querySelector('button[type=reset]')!.addEventListener('click', () => docDialog.close()); for (const dia of [msgDialog, createDialog, exportDialog, docDialog]) dia.addEventListener('click', (ev) => { const rect = dia.getBoundingClientRect(); if ( !( rect.top <= ev.clientY && ev.clientY <= rect.top + rect.height && rect.left <= ev.clientX && ev.clientX <= rect.left + rect.width ) ) { dia.close(); } }); window.addEventListener('popstate', (e) => { const dataset = (e.state as HistoryState | null)?.dataset; if (dataset && dataset !== this.rx.dataset && this.getDataset(dataset) !== undefined) Loading @@ -603,27 +585,35 @@ export class App { this.state.viewport.zoomFit('h'); }); document .querySelector('#button-reset-controls')! .addEventListener('click', () => Object.values(this.state.controls).forEach((c) => c.reset())); document.querySelector('#button-reset-lightball')!.addEventListener('click', () => { this.state.lightphi = LIGHT_ANGLE_DEFAULTS.lightphi; this.state.lighttheta = LIGHT_ANGLE_DEFAULTS.lighttheta; this.requestRender(); }); const requestFullscreen = this.rx.requestFullscreen = document.fullscreenEnabled || (document as any).webkitFullscreenEnabled ? ( document.documentElement.requestFullscreen || (document.documentElement as any).webkitRequestFullscreen )?.bind(document.documentElement) : null; if (requestFullscreen) { const fsBtn = document.querySelector('button#button-fullscreen') as HTMLButtonElement; fsBtn.classList.remove('hidden'); fsBtn.addEventListener('click', () => requestFullscreen()); } public resetLightball() { this.state.lightphi = LIGHT_ANGLE_DEFAULTS.lightphi; this.state.lighttheta = LIGHT_ANGLE_DEFAULTS.lighttheta; this.requestRender(); } public resetControls() { Object.values(this.state.controls).forEach((c) => c.reset()); } public onDialogClick(ev: MouseEvent) { const dia = ev.currentTarget as HTMLDialogElement; const rect = dia.getBoundingClientRect(); if ( rect.top > ev.clientY || ev.clientY > rect.top + rect.height || rect.left > ev.clientX || ev.clientX > rect.left + rect.width ) { dia.close(); } } Loading src/gl-program.ts +3 −12 Changes for src/gl-program.ts: 3 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -2,9 +2,9 @@ import rlghtVtxSource from './shaders/relight.vert'; import rlghtFrgSource from './shaders/relight.frag'; import lghtbllVtxSource from './shaders/lightball.vert'; import lghtbllFrgSource from './shaders/lightball.frag'; import {mat4, vec2, vec3, vec4} from "gl-matrix"; import {mat4, vec2, vec3} from "gl-matrix"; import {Controls, CONTROLS_SET_UNIFORMS, State} from "./control.ts"; import {Exported, GL, matToString, roundTo} from "./utils.ts"; import { DEBUG, Exported, GL } from './utils.ts'; import {Rect, TextureSource, TextureTile} from "./tiling.ts"; Loading Loading @@ -123,7 +123,7 @@ abstract class GLProgramBase<Attribs extends string[], Uniforms extends string[] private loadShader(type: GLenum, source: string) { const shader = this.gl.createShader(type)!; if (import.meta.env.DEV) if (DEBUG) source = '#define DEBUG 1\n' + source // Send the source to the shader object Loading Loading @@ -314,21 +314,12 @@ export class LightballProgram extends GLProgramBase<[keyof typeof LightballProgr mat4.scale(projection, projection, [2 / gl.drawingBufferWidth, 2 / gl.drawingBufferHeight, 1]); output += `Lightball projection matrices:\n${matToString(projection)}\n`; let start = vec2.fromValues(...LightballProgram.POSITION.start), center = vec2.add(vec2.create(), vec2.div([0, 0], LightballProgram.POSITION.size, [2, 2]), start), end = vec2.add(vec2.create(), LightballProgram.POSITION.size, start), radius = Math.min(...LightballProgram.POSITION.size) / 2, circle = vec3.fromValues(center[0], center[1], radius), alpha = LightballProgram.ALPHA[data.pointerState.overLightball ? (data.pointerState.dragging ? 'active' : 'hover') : 'inactive']; output += `\tstart: ${roundTo(start)} -> ${roundTo(vec2.transformMat4([0, 0], start, projection))}\n` + `\tend: ${roundTo(end)} -> ${roundTo(vec2.transformMat4([0, 0], end, projection))}\n` + `\tcenter: ${roundTo(center)} -> ${roundTo(vec2.transformMat4([0, 0], center, projection))}\n` + `\tradius: ${roundTo(radius)} -> ${roundTo(vec4.transformMat4([0, 0, 0, 0], [radius, 0, 0, 0], projection)[0])}\n` + `\talpha: ${alpha}\n`; gl.uniformMatrix4fv( this.uniformLocations.projectionMatrix, false, projection); Loading src/lang/fr.yaml +3 −3 Changes for src/lang/fr.yaml: 3 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -59,11 +59,11 @@ help: title: Comment ça marche text: |- L'application charge deux types d'image : l'albédo/couleur de la texture (une photo classique), et sa <a href="https://fr.wikipedia.org/wiki/Normal_mapping">carte de normales</a> (vous pouvez voir ces images de base <a target="_blank" href="https://fr.wikipedia.org/wiki/Normal_mapping">carte de normales</a> (vous pouvez voir ces images de base avec les modes de rendu <em>albedo</em> et <em>carte de normales</em>). Elle utilise ensuite des <a href="https://en.wikipedia.org/wiki/Shader">Shaders 3D</a> pour recalculer la quantité de lumière sur chaque <a target="_blank" href="https://en.wikipedia.org/wiki/Shader">Shaders 3D</a> pour recalculer la quantité de lumière sur chaque pixel de la texture en utilisant la technique de <a href="https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model">l'Ombrage de Blinn-Phong</a>. <a target="_blank" href="https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model">l'Ombrage de Blinn-Phong</a>. howto: title: Guide d'utilisation Loading src/tiling.ts +181 −133 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
index.html +16 −14 Changes for index.html: 16 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -19,23 +19,25 @@ <label for="select-dataset">{{t('controls.selected')}}</label> <span>{{t('colon')}}</span> <select name="dataset" id="select-dataset" class="btn-dark" :disabled="datasetsDisabled" :value="dataset" @change="onDatasetSelect($event)"> :value="dataset" @change="app.onDatasetSelect($event)"> <optgroup label="" :label="t('controls.hosted')" class="builtins"> <option v-for="ds in datasets.server" :value="ds.id">{{getLocString(ds.name, locale)}}</option> <option v-for="ds in datasets.server" :value="ds.id">{{loc(ds.name, locale)}}</option> </optgroup> <optgroup label="" :label="t('controls.inmemory')" class="inmemory"> <option v-for="ds in datasets.memory" :value="ds.id">{{getLocString(ds.name, locale)}}</option> <option v-for="ds in datasets.memory" :value="ds.id">{{loc(ds.name, locale)}}</option> <option class="action" value="create">{{t('controls.createinmemory')}}</option> </optgroup> </select> <button id="button-delete-dataset" class="hidden btn-dark">{{t('controls.deleteinmemory')}} <button id="button-delete-dataset" class="btn-dark" v-show="dataset.startsWith('memory:')" @click="app.deleteMemoryDataset(dataset)"> {{t('controls.deleteinmemory')}} </button> </div> <div> <button class="btn-dark btn-icon hidden" id="button-fullscreen" aria-label="Fullscreen" title="Fullscreen" data-icon="fullscreen"></button> <button class="btn-dark btn-icon" aria-label="Fullscreen" title="Fullscreen" data-icon="fullscreen" v-show="requestFullscreen" @click="requestFullscreen"></button> <select aria-label="Language" class="btn-dark" name="lang" :value="locale" @change="setLocale($event.target.value)"> :value="locale" @change="app.setLocale($event.target.value)"> <option v-for="lang in locales" :value="lang.id">{{lang.name}}</option> </select> <button class="btn-dark" id="button-help"> Loading Loading @@ -101,14 +103,14 @@ <div class="description-container"> <div v-if="description"> <h3>{{t('controls.description')}}</h3> <div class="content">{{ description && getLocString(description, locale) }}</div> <div class="content">{{ description && loc(description, locale) }}</div> </div> <pre v-if="output"><code>{{ output }}</code></pre> </div> <div class="lightball-container"> <canvas id="ivoire-lightball"></canvas> <div class="reset-button-container"> <button class="btn-dark btn-icon" id="button-reset-lightball" <button class="btn-dark btn-icon" @click="app.resetLightball()" :title="t('controls.resetlightball')" data-icon="reset" v-if="phase==='render'"></button> </div> </div> Loading @@ -129,12 +131,12 @@ </main> </div> <dialog id="message" class="drop-shadow"> <dialog id="message" class="drop-shadow" @click="app.onDialogClick"> <h1></h1> <p></p> <button class="btn-dark" type="reset" autofocus>{{t('ok')}}</button> <button class="btn-dark" type="reset" autofocus @click="$el.parent.close()">{{t('ok')}}</button> </dialog> <dialog id="create-inmemory" class="drop-shadow"> <dialog id="create-inmemory" class="drop-shadow" @click="app.onDialogClick"> <h1>{{t('memcreate.title')}}</h1> <form action="#"> <label for="upload-dataset-name" autofocus> Loading @@ -154,7 +156,7 @@ <button class="btn" type="reset">{{t('cancel')}}</button> <button class="btn" type="submit">{{t('memcreate.create')}}</button> </dialog> <dialog id="export-success"> <dialog id="export-success" @click="app.onDialogClick"> <h3>{{t('export.success')}}</h3> <img src="" alt="Exported image"> <div> Loading @@ -163,7 +165,7 @@ </div> <a href="" id="export-download-anchor" class="hidden"></a> </dialog> <dialog id="documentation"> <dialog id="documentation" @click="app.onDialogClick"> <button type="reset" class="btn-close btn-icon" data-icon="close"></button> <div class="logos"> <a target="_blank" href="https://www.greyc.fr"> Loading
src/app.ts +55 −65 Changes for src/app.ts: 55 added lines, 65 removed lines. Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ import { WebGLCanvasRenderingContext, } from './gl-program.ts'; import {ControlUpdateEvent, getStateDefaults, LIGHT_ANGLE_DEFAULTS, State} from './control.ts'; import {catchError, exportValues, getLightSourceVec, matToString, roundTo} from './utils.ts'; import {catchError, exportValues, getLightSourceVec, matToString, roundTo, VERBOSE} from './utils.ts'; import {DownloadManager} from './download.ts'; import {Dataset, MemoryDataset, SingleDataset, TiledDataset} from './dataset.ts'; import {ExportWorkerData, ExportWorkerResult} from './workers/export.ts'; Loading Loading @@ -76,7 +76,6 @@ export class App { this.initControls(); this.rx.phase = 'load-datasets'; if (!import.meta.env.VITE_DATASET_FILES) throw new Error('Missing VITE_DATASET_FILES from environment'); let selected: string | undefined = undefined; if (history.state) { selected = (history.state as HistoryState).dataset || undefined; Loading @@ -84,6 +83,7 @@ export class App { selected = window.location.hash.substring(1); } if (!import.meta.env.VITE_DATASET_FILES) throw new Error('Missing VITE_DATASET_FILES from environment'); const datasetUrls = (import.meta.env.VITE_DATASET_FILES as string) .split(',') .map((u) => new URL(u, document.location.href)); Loading Loading @@ -147,21 +147,30 @@ export class App { } private get DEFAULT_RX_STATE() { // noinspection JSUnusedGlobalSymbols // Give access to those methods const fn = Object.fromEntries([ this.getLocString, this.setLocale, this.deleteMemoryDataset, this.onDatasetSelect, this.onDialogClick, this.resetLightball, this.resetControls, ].map((m) => [m.name, m.bind(this)] as const)); return { fn, phase: 'initial' as RunPhase, datasets: { server: new Array<{ id: string; name: LocString }>(), memory: new Array<{ id: string; name: LocString }>(), }, datasetsDisabled: false, onDatasetSelect: this.onDatasetSelect.bind(this), dataset: '', requestFullscreen: null as (() => void) | null, t: (k: string) => this.i18n.t(k), loc: this.getLocString.bind(this), locale: this.i18n.locale, locales: App.LANGS.map((id) => ({ id, name: this.i18n.translations[id].lang as string })), setLocale: (locale: string) => localStorage.setItem('lang', (this.i18n.locale = locale)), getLocString: this.getLocString.bind(this), description: null as LocString | null, output: null as string | null, }; Loading @@ -171,11 +180,16 @@ export class App { let text = typeof value === 'string' ? value : value[locale] || value[this.i18n.defaultLocale]!; if (!text) { console.error('Missing locale'); return "[Error: missing locale]"; return '[Error: missing locale]'; } return text; } public setLocale(locale: string) { this.i18n.locale = locale; localStorage.setItem('lang', locale); } get datasets(): Dataset[] { return new Array<Dataset>().concat(...Object.values(this._datasets)); } Loading @@ -194,15 +208,15 @@ export class App { private onDatasetSelect(ev: InputEvent) { const newValue = (ev.target as HTMLSelectElement).value; if (newValue === 'create') { if (this.rx.dataset === 'create') return; let dia = document.querySelector('dialog#create-inmemory')! as HTMLDialogElement; dia.returnValue = ''; dia.showModal(); this.setDataset(""); this.rx.dataset = "create"; this.setDataset(''); this.rx.dataset = 'create'; this.rx.phase = 'create-inmemory'; this.requestRender(); } else this.setDataset(newValue); } else this.setDataset(newValue); } private setDataset(dsId: string) { Loading Loading @@ -272,9 +286,6 @@ export class App { } this._source = value; if (value && 'dataset' in value) { document .querySelector('#button-delete-dataset')! .classList.toggle('hidden', value.dataset.type !== 'memory'); this.state.viewport.updateCanvasMat(this.gls.relight.canvas, value.totalSize, true); this.rx.phase = 'render'; this.requestRender(); Loading Loading @@ -379,7 +390,7 @@ export class App { this.programs.relight.clearCanvas(); } if (import.meta.env.DEV && import.meta.env.VITE_VERBOSE === '1') this.rx.output = output; if (VERBOSE) this.rx.output = output; } private async updateRenderArea(now: boolean) { Loading Loading @@ -465,21 +476,16 @@ export class App { } private deleteMemoryDataset(id: string) { let dsId = this._datasets.memory.findIndex((d) => d.id === id); if (dsId === -1) throw new Error(`Unknown dataset id: ${id}`); this._datasets.memory.splice(dsId, 1)[0].delete(); if (!id.startsWith('memory:')) return console.error(`Non-memory dataset id: ${id}`); let idPart = id.substring('memory:'.length), index = this._datasets.memory.findIndex((d) => d.id === idPart); if (index === -1) return console.error(`Unknown memory dataset id: ${idPart}`); console.debug(`Deleting memory dataset ${idPart}`); this._datasets.memory.splice(index, 1)[0].delete(); this.updateDatasets(); } private initControls(): void { const sheet = document.styleSheets[0]; for (const phase of RUN_PHASES) { sheet.insertRule( `.controls:not([data-phase=${phase}]) *[data-phase=${phase}] { display: none !important; }`, sheet.cssRules.length, ); } for (let ctrl of Object.values(this.state.controls)) { ctrl.addEventListener(ControlUpdateEvent.type, () => this.requestRender()); ctrl.reset(); Loading @@ -495,15 +501,6 @@ export class App { new ResizeObserver(this.onResize.bind(this)).observe(this.gls.relight.canvas); const delMemBtn = document.querySelector('button#button-delete-dataset')!; delMemBtn.addEventListener( 'click', () => this.currentDataset && this.deleteMemoryDataset(this.currentDataset.id), ); const msgDialog = document.querySelector('dialog#message') as HTMLDialogElement; msgDialog.querySelector('button[type=reset]')!.addEventListener('click', () => msgDialog.close()); const createDialog = document.querySelector('dialog#create-inmemory') as HTMLDialogElement; createDialog.querySelector('button[type=reset]')!.addEventListener('click', () => createDialog.close()); createDialog.querySelector('button[type=submit]')!.addEventListener('click', (ev) => { Loading Loading @@ -572,21 +569,6 @@ export class App { document.querySelector('#button-help')!.addEventListener('click', () => docDialog.showModal()); docDialog.querySelector('button[type=reset]')!.addEventListener('click', () => docDialog.close()); for (const dia of [msgDialog, createDialog, exportDialog, docDialog]) dia.addEventListener('click', (ev) => { const rect = dia.getBoundingClientRect(); if ( !( rect.top <= ev.clientY && ev.clientY <= rect.top + rect.height && rect.left <= ev.clientX && ev.clientX <= rect.left + rect.width ) ) { dia.close(); } }); window.addEventListener('popstate', (e) => { const dataset = (e.state as HistoryState | null)?.dataset; if (dataset && dataset !== this.rx.dataset && this.getDataset(dataset) !== undefined) Loading @@ -603,27 +585,35 @@ export class App { this.state.viewport.zoomFit('h'); }); document .querySelector('#button-reset-controls')! .addEventListener('click', () => Object.values(this.state.controls).forEach((c) => c.reset())); document.querySelector('#button-reset-lightball')!.addEventListener('click', () => { this.state.lightphi = LIGHT_ANGLE_DEFAULTS.lightphi; this.state.lighttheta = LIGHT_ANGLE_DEFAULTS.lighttheta; this.requestRender(); }); const requestFullscreen = this.rx.requestFullscreen = document.fullscreenEnabled || (document as any).webkitFullscreenEnabled ? ( document.documentElement.requestFullscreen || (document.documentElement as any).webkitRequestFullscreen )?.bind(document.documentElement) : null; if (requestFullscreen) { const fsBtn = document.querySelector('button#button-fullscreen') as HTMLButtonElement; fsBtn.classList.remove('hidden'); fsBtn.addEventListener('click', () => requestFullscreen()); } public resetLightball() { this.state.lightphi = LIGHT_ANGLE_DEFAULTS.lightphi; this.state.lighttheta = LIGHT_ANGLE_DEFAULTS.lighttheta; this.requestRender(); } public resetControls() { Object.values(this.state.controls).forEach((c) => c.reset()); } public onDialogClick(ev: MouseEvent) { const dia = ev.currentTarget as HTMLDialogElement; const rect = dia.getBoundingClientRect(); if ( rect.top > ev.clientY || ev.clientY > rect.top + rect.height || rect.left > ev.clientX || ev.clientX > rect.left + rect.width ) { dia.close(); } } Loading
src/gl-program.ts +3 −12 Changes for src/gl-program.ts: 3 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -2,9 +2,9 @@ import rlghtVtxSource from './shaders/relight.vert'; import rlghtFrgSource from './shaders/relight.frag'; import lghtbllVtxSource from './shaders/lightball.vert'; import lghtbllFrgSource from './shaders/lightball.frag'; import {mat4, vec2, vec3, vec4} from "gl-matrix"; import {mat4, vec2, vec3} from "gl-matrix"; import {Controls, CONTROLS_SET_UNIFORMS, State} from "./control.ts"; import {Exported, GL, matToString, roundTo} from "./utils.ts"; import { DEBUG, Exported, GL } from './utils.ts'; import {Rect, TextureSource, TextureTile} from "./tiling.ts"; Loading Loading @@ -123,7 +123,7 @@ abstract class GLProgramBase<Attribs extends string[], Uniforms extends string[] private loadShader(type: GLenum, source: string) { const shader = this.gl.createShader(type)!; if (import.meta.env.DEV) if (DEBUG) source = '#define DEBUG 1\n' + source // Send the source to the shader object Loading Loading @@ -314,21 +314,12 @@ export class LightballProgram extends GLProgramBase<[keyof typeof LightballProgr mat4.scale(projection, projection, [2 / gl.drawingBufferWidth, 2 / gl.drawingBufferHeight, 1]); output += `Lightball projection matrices:\n${matToString(projection)}\n`; let start = vec2.fromValues(...LightballProgram.POSITION.start), center = vec2.add(vec2.create(), vec2.div([0, 0], LightballProgram.POSITION.size, [2, 2]), start), end = vec2.add(vec2.create(), LightballProgram.POSITION.size, start), radius = Math.min(...LightballProgram.POSITION.size) / 2, circle = vec3.fromValues(center[0], center[1], radius), alpha = LightballProgram.ALPHA[data.pointerState.overLightball ? (data.pointerState.dragging ? 'active' : 'hover') : 'inactive']; output += `\tstart: ${roundTo(start)} -> ${roundTo(vec2.transformMat4([0, 0], start, projection))}\n` + `\tend: ${roundTo(end)} -> ${roundTo(vec2.transformMat4([0, 0], end, projection))}\n` + `\tcenter: ${roundTo(center)} -> ${roundTo(vec2.transformMat4([0, 0], center, projection))}\n` + `\tradius: ${roundTo(radius)} -> ${roundTo(vec4.transformMat4([0, 0, 0, 0], [radius, 0, 0, 0], projection)[0])}\n` + `\talpha: ${alpha}\n`; gl.uniformMatrix4fv( this.uniformLocations.projectionMatrix, false, projection); Loading
src/lang/fr.yaml +3 −3 Changes for src/lang/fr.yaml: 3 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -59,11 +59,11 @@ help: title: Comment ça marche text: |- L'application charge deux types d'image : l'albédo/couleur de la texture (une photo classique), et sa <a href="https://fr.wikipedia.org/wiki/Normal_mapping">carte de normales</a> (vous pouvez voir ces images de base <a target="_blank" href="https://fr.wikipedia.org/wiki/Normal_mapping">carte de normales</a> (vous pouvez voir ces images de base avec les modes de rendu <em>albedo</em> et <em>carte de normales</em>). Elle utilise ensuite des <a href="https://en.wikipedia.org/wiki/Shader">Shaders 3D</a> pour recalculer la quantité de lumière sur chaque <a target="_blank" href="https://en.wikipedia.org/wiki/Shader">Shaders 3D</a> pour recalculer la quantité de lumière sur chaque pixel de la texture en utilisant la technique de <a href="https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model">l'Ombrage de Blinn-Phong</a>. <a target="_blank" href="https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model">l'Ombrage de Blinn-Phong</a>. howto: title: Guide d'utilisation Loading