Commit b28f34a9 authored by Laureline Nevin's avatar Laureline Nevin ⌨️
Browse files

More petite-vue integration

parent 997fd6b7
Loading
Loading
Loading
Loading
+16 −14
Changes for index.html: 16 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -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">
@@ -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>
@@ -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>
@@ -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>
@@ -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">
+55 −65
Changes for src/app.ts: 55 added lines, 65 removed lines.
Original line number Diff line number Diff line
@@ -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';
@@ -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;
@@ -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));
@@ -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,
        };
@@ -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));
    }
@@ -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) {
@@ -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();
@@ -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) {
@@ -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();
@@ -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) => {
@@ -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)
@@ -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();
        }
    }

+3 −12
Changes for src/gl-program.ts: 3 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -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";


@@ -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
@@ -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);

+3 −3
Changes for src/lang/fr.yaml: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -59,11 +59,11 @@ help:
    title: Comment ça marche
    text: |-
      L'application charge deux types d'image&nbsp;: 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
+181 −133

File changed.

Preview size limit exceeded, changes collapsed.

Loading