5180 fichiers 38.5 Mo 0 téléchargements 206 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / api / camera / blitRoomCanvasToViewfinder.ts
blitRoomCanvasToViewfinder.ts 5.9 Ko Télécharger le fichier
import { GetRenderer, GetRoomEngine, OctaneRectangle, TextureUtils } from '@octane/renderer';

/**
 * AIR CameraViewFinder.update() snapshots the room display object
 * (snapshotRoomCanvasToBitmap), not the WebGL backbuffer. Copying
 * GetRenderer().canvas every animation frame flickers because the
 * drawing buffer is cleared after present.
 *
 * generateTexture.frame is in the room master's local pixels. Map the
 * viewfinder from CSS into that space: canvas.width / resolution is the
 * logical renderer size (not the backing-store scale), then invert the
 * master's world transform when it is not identity.
 *
 * Pass explicit width/height (AIR 320×320 / 110×110) so a subpixel CSS
 * rect cannot resize and clear the 2D canvas.
 *
 * The readback is the expensive part, so it only happens when the room
 * canvas reported a content change since the last blit or the viewfinder
 * frame moved. `canvasUpdated` only describes the most recent render, so
 * callers invoke this every animation frame and pass `minIntervalMs` to
 * keep their cadence; the flag is sampled on every call and accumulated
 * until the next blit is due.
 */
interface ViewfinderState {
    dirty: boolean;
    frameKey: string;
    lastBlitAt: number;
}

const viewfinderStates = new WeakMap<HTMLCanvasElement, ViewfinderState>();

const getViewfinderState = (target: HTMLCanvasElement): ViewfinderState =>
{
    let state = viewfinderStates.get(target);

    if(!state)
    {
        state = { dirty: true, frameKey: '', lastBlitAt: -Infinity };
        viewfinderStates.set(target, state);
    }

    return state;
};

const toMasterLocal = (master: { worldTransform?: { applyInverse?: (point: { x: number; y: number }) => { x: number; y: number } } }, screenX: number, screenY: number): { x: number; y: number } =>
{
    const inverse = master?.worldTransform?.applyInverse;

    if(typeof inverse === 'function')
    {
        const point = inverse.call(master.worldTransform, { x: screenX, y: screenY });

        return { x: point.x, y: point.y };
    }

    return { x: screenX, y: screenY };
};

export const getViewfinderRoomFrame = (target: HTMLCanvasElement | null, width?: number, height?: number): InstanceType<typeof OctaneRectangle> | null =>
{
    if(!target) return null;

    try
    {
        const master = GetRoomEngine()?.getActiveRoomInstanceRenderingCanvas?.()?.master;
        const renderer = GetRenderer();
        const source = renderer?.canvas as HTMLCanvasElement | undefined;

        if(!master || !source) return null;

        const srcRect = source.getBoundingClientRect();
        const dstRect = target.getBoundingClientRect();

        if(srcRect.width <= 0 || srcRect.height <= 0 || dstRect.width <= 0 || dstRect.height <= 0) return null;

        const bufferWidth = width ?? Math.max(1, Math.round(dstRect.width));
        const bufferHeight = height ?? Math.max(1, Math.round(dstRect.height));
        const resolution = Number(renderer?.resolution) || 1;
        const logicalWidth = (source.width > 0) ? (source.width / resolution) : srcRect.width;
        const logicalHeight = (source.height > 0) ? (source.height / resolution) : srcRect.height;
        const scaleX = logicalWidth / srcRect.width;
        const scaleY = logicalHeight / srcRect.height;
        const screenX = (dstRect.left - srcRect.left) * scaleX;
        const screenY = (dstRect.top - srcRect.top) * scaleY;
        const screenW = bufferWidth * scaleX;
        const screenH = bufferHeight * scaleY;
        const topLeft = toMasterLocal(master, screenX, screenY);
        const bottomRight = toMasterLocal(master, screenX + screenW, screenY + screenH);
        const x = Math.round(Math.min(topLeft.x, bottomRight.x));
        const y = Math.round(Math.min(topLeft.y, bottomRight.y));
        const frameWidth = Math.max(1, Math.round(Math.abs(bottomRight.x - topLeft.x)));
        const frameHeight = Math.max(1, Math.round(Math.abs(bottomRight.y - topLeft.y)));

        return new OctaneRectangle(x, y, frameWidth, frameHeight);
    }
    catch
    {
        return null;
    }
};

export const blitRoomCanvasToViewfinder = (target: HTMLCanvasElement | null, width?: number, height?: number, minIntervalMs: number = 0, now: number = performance.now()): boolean =>
{
    if(!target) return false;
    if((typeof document !== 'undefined') && document.hidden) return false;

    try
    {
        const roomCanvas = GetRoomEngine()?.getActiveRoomInstanceRenderingCanvas?.();
        const master = roomCanvas?.master;

        if(!master) return false;

        const state = getViewfinderState(target);

        if(roomCanvas.canvasUpdated) state.dirty = true;

        if((now - state.lastBlitAt) < minIntervalMs) return false;

        const frame = getViewfinderRoomFrame(target, width, height);

        if(!frame) return false;

        const bufferWidth = width ?? frame.width;
        const bufferHeight = height ?? frame.height;
        const frameKey = `${ frame.x },${ frame.y },${ frame.width },${ frame.height },${ bufferWidth },${ bufferHeight }`;

        if(!state.dirty && (frameKey === state.frameKey)) return false;

        if(target.width !== bufferWidth) target.width = bufferWidth;
        if(target.height !== bufferHeight) target.height = bufferHeight;

        const snapshot = TextureUtils.generateTexture({
            target: master,
            frame,
            resolution: 1
        });

        if(!snapshot) return false;

        const extracted = TextureUtils.generateCanvas(snapshot) as CanvasImageSource | undefined;

        snapshot.destroy?.(true);

        const context = target.getContext('2d');

        if(!context || !extracted) return false;

        context.imageSmoothingEnabled = false;
        context.fillStyle = '#000000';
        context.fillRect(0, 0, bufferWidth, bufferHeight);
        context.drawImage(extracted, 0, 0, bufferWidth, bufferHeight);

        state.dirty = false;
        state.frameKey = frameKey;
        state.lastBlitAt = now;

        return true;
    }
    catch
    {
        return false;
    }
};
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser