OutlinePassProxy.js

import Check from "@lijuhong1981/jscheck/src/Check.js";
import { Color, Scene } from "three";
import OutlineNode, { outline } from './OutlineNode.js';
import { oscSine, time, uniform } from "three/tsl";
import { TextureNode } from "three/webgpu";
import PassNodeProxy from "./PassNodeProxy.js";

/**
 * OutlinePassProxy是PassNodeProxy的一个子类,用于代理Outline(轮廓)后处理特效的Pass节点。
 * @class
 * @extends PassNodeProxy<OutlineNode>
*/
class OutlinePassProxy extends PassNodeProxy {
    constructor(library) {
        super(library);
        this._selectedObjects = [];
        this._downSampleRatio = 2;
        this._uniformEdgeThickness = uniform(1);
        this._uniformEdgeGlow = uniform(0);
        this._uniformeEdgeStrength = uniform(3.0);
        this._uniformPulsePeriod = uniform(0);
        this._uniformVisibleEdgeColor = uniform(new Color(0xffffff));
        this._uniformHiddenEdgeColor = uniform(new Color(0));
        this._uniformHighlightColor = uniform(new Color(0x0000ff));
        this._uniformHighlightStrength = uniform(0);
    }
    /**
     * An array of selected objects.
     * @type {Array<Object3D>}
    */
    set selectedObjects(value) {
        this._selectedObjects = value ?? [];
        this.node && (this.node.selectedObjects = this._selectedObjects);
    }
    get selectedObjects() {
        return this._selectedObjects;
    }
    /**
     * The downsample ratio.
     * @type {number}
     * @default 2
    */
    set downSampleRatio(value) {
        Check.typeOf.number('downSampleRatio', value);
        this._downSampleRatio = value;
        this.node && (this.node.downSampleRatio = value);
    }
    get downSampleRatio() {
        return this._downSampleRatio;
    }
    /**
     * The thickness of the edges.
     * @type {number}
     * @default 1
    */
    set edgeThickness(value) {
        Check.typeOf.number('edgeThickness', value);
        this._uniformEdgeThickness.value = value;
    }
    get edgeThickness() {
        return this._uniformEdgeThickness.value;
    }
    /**
     * Can be used for an animated glow/pulse effect.
     * @type {number}
     * @default 0
    */
    set edgeGlow(value) {
        Check.typeOf.number('edgeGlow', value);
        this._uniformEdgeGlow.value = value;
    }
    get edgeGlow() {
        return this._uniformEdgeGlow.value;
    }
    /**
     * The strength of the edges.
     * @type {number}
     * @default 3
    */
    set edgeStrength(value) {
        Check.typeOf.number('edgeStrength', value);
        this._uniformeEdgeStrength.value = value;
    }
    get edgeStrength() {
        return this._uniformeEdgeStrength.value;
    }
    /**
     * The period of the pulse effect in seconds. Set to 0 to disable the pulse effect.
     * @type {number}
     * @default 0
    */
    set pulsePeriod(value) {
        Check.typeOf.number('pulsePeriod', value);
        this._uniformPulsePeriod.value = value;
    }
    get pulsePeriod() {
        return this._uniformPulsePeriod.value;
    }
    /**
     * The color of visible edges.
     * @param {Color|string|number} value - The color value, It can be a Color instance, a css color string, or a hexadecimal color number.
    */
    set visibleEdgeColor(value) {
        Check.defined('visibleEdgeColor', value);
        this._uniformVisibleEdgeColor.value.set(value);
    }
    /**
     * The color of visible edges.
     * @returns {Color}
    */
    get visibleEdgeColor() {
        return this._uniformVisibleEdgeColor.value;
    }
    /**
     * The color of hidden edges.
     * @param {Color|string|number} value - The color value, It can be a Color instance, a css color string, or a hexadecimal color number.
    */
    set hiddenEdgeColor(value) {
        Check.defined('hiddenEdgeColor', value);
        this._uniformHiddenEdgeColor.value.set(value);
    }
    /**
     * The color of hidden edges.
     * @returns {Color}
    */
    get hiddenEdgeColor() {
        return this._uniformHiddenEdgeColor.value;
    }
    /**
     * The highlight color for the selected objects.
     * @param {Color|string|number} value - The color value, It can be a Color instance, a css color string, or a hexadecimal color number.
    */
    set highlightColor(value) {
        Check.defined('highlightColor', value);
        this._uniformHighlightColor.value.set(value);
    }
    /**
     * The highlight color for the selected objects.
     * @returns {Color}
    */
    get highlightColor() {
        return this._uniformHighlightColor.value;
    }
    /**
     * The strength of the highlight on the selected objects.
     * @type {number}
     * @default 0
    */
    set highlightStrength(value) {
        Check.typeOf.number('highlightStrength', value);
        this._uniformHighlightStrength.value = value;
    }
    get highlightStrength() {
        return this._uniformHighlightStrength.value;
    }
    /**
     * Whether to use highlight for the selected objects. When set to true, the highlightStrength will be set to 1, otherwise it will be set to 0.
     * @type {boolean}
     * @default false
     * @deprecated Use highlightStrength instead to control the strength of the highlight.
    */
    set useHighlight(value) {
        Check.typeOf.boolean('useHighlight', value);
        this.highlightStrength = value ? 1 : 0;
    }
    get useHighlight() {
        return this.highlightStrength > 0;
    }
    /**
     * 构建Outline Pass节点。
     * @param {Scene} scene - 场景对象
     * @param {Camera} camera - 相机对象
     * @param {TextureNode} outputNode - 输出节点对象
     * @returns {TextureNode} 处理后的节点对象
     * @override
    */
    build(scene, camera, outputNode) {
        const outlinePass = this.node = outline(scene, camera, {
            selectedObjects: this._selectedObjects,
            edgeThickness: this._uniformEdgeThickness,
            edgeGlow: this._uniformEdgeGlow,
            downSampleRatio: this._downSampleRatio,
        });
        outlinePass.toInspector('Outline');

        const { visibleEdge, hiddenEdge, visibleSelected } = outlinePass;
        // 边缘颜色由可见边缘和隐藏边缘的颜色混合而成,并根据边缘强度进行调整。
        const edgeColor = visibleEdge.mul(this._uniformVisibleEdgeColor).add(hiddenEdge.mul(this._uniformHiddenEdgeColor)).mul(this._uniformeEdgeStrength);
        // 高亮颜色由可见选中区域的颜色乘以高亮颜色和高亮强度得到。
        const highlightColor = visibleSelected.mul(this._uniformHighlightColor).mul(this._uniformHighlightStrength);
        // 脉冲效果通过一个正弦函数来实现,周期由pulsePeriod控制。当pulsePeriod大于0时,边缘颜色会在原有基础上乘以osc(在0.5到1之间变化),从而产生脉冲效果;当pulsePeriod为0时,边缘颜色保持不变。
        const period = time.div(this._uniformPulsePeriod).mul(2);
        const osc = oscSine(period).mul(.5).add(.5); // osc [ 0.5, 1.0 ]
        const edgePulse = this._uniformPulsePeriod.greaterThan(0).select(edgeColor.mul(osc), edgeColor);
        // 最终的颜色是边缘颜色(可能带有脉冲效果)和高亮颜色的叠加。
        const finalColor = edgePulse.add(highlightColor);
        // 叠加混合
        return outputNode.add(finalColor);
    }
};

export default OutlinePassProxy;
export { OutlinePassProxy };