UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.

210 lines (184 loc) • 6.78 kB
import { AlwaysStencilFunc, DecrementStencilOp, DecrementWrapStencilOp, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, IncrementStencilOp, IncrementWrapStencilOp, InvertStencilOp, KeepStencilOp, LessEqualStencilFunc, LessStencilFunc, // stencil funcs NeverStencilFunc, NotEqualStencilFunc, ReplaceStencilOp, type StencilFunc, type StencilOp as ThreeStencilOp, // stencil ops ZeroStencilOp, } from "three"; import { type GLTF, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js"; import { showBalloonWarning } from "../debug/index.js"; import { isUsingInstancing } from "../engine_gameobject.js"; import { isLocalNetwork } from "../engine_networking_utils.js"; import { type SourceIdentifier } from "../engine_types.js"; import { type IComponent as Component, type IRenderer } from "../engine_types.js"; import { getParam } from "../engine_utils.js"; const debug = getParam("debugstencil"); function matchesLayer(stencilLayer: number, comp: Component): boolean { return (stencilLayer & 1 << comp.layer) != 0; } declare type StencilSettingsModel = { name: string; event: number; index: number; queue: number; layer: number; value: number; compareFunc: number; passOp: number; failOp: number; zFailOp: number; } const $stencils = Symbol("stencils"); export class NEEDLE_render_objects implements GLTFLoaderPlugin { public get name() { return "NEEDLE_render_objects"; } private static stencils: { [key: string]: StencilSettingsModel[] } = {}; static applyStencil(obj?: IRenderer | null) { if (!obj) return; const source = obj.sourceId; if (debug) console.log(source, NEEDLE_render_objects.stencils); if (!source) return; const settings = NEEDLE_render_objects.stencils[source]; if (!settings) return; for (let i = settings.length - 1; i >= 0; i--) { const stencil: StencilSettingsModel = settings[i]; if (matchesLayer(stencil.layer, obj)) { if (debug) console.log(stencil); setTimeout(() => { if (isLocalNetwork() && isUsingInstancing(obj.gameObject)) { showBalloonWarning("Stencil not supported on instanced objects"); console.warn("Stencil not supported on instanced objects", obj); } }, 500) for (let i = 0; i < obj.sharedMaterials.length; i++) { let mat = obj.sharedMaterials[i]; if (mat) { // if (!mat[$stencils]) mat = mat.clone(); mat[$stencils] = true; mat.stencilWrite = true; mat.stencilWriteMask = 255; mat.stencilFuncMask = 255; mat.stencilRef = stencil.value; mat.stencilFunc = stencil.compareFunc as StencilFunc; mat.stencilZPass = stencil.passOp as ThreeStencilOp; mat.stencilFail = stencil.failOp as ThreeStencilOp; mat.stencilZFail = stencil.zFailOp as ThreeStencilOp; obj.sharedMaterials[i] = mat; } } // you can have 50 renderer features per event until this breaks obj.gameObject.renderOrder = stencil.event * 1000 + stencil.index * 50; break; } } } private parser: GLTFParser; private source: SourceIdentifier; constructor(parser: GLTFParser, source: SourceIdentifier) { this.parser = parser; this.source = source; } afterRoot(_result: GLTF): Promise<void> | null { const extensions = this.parser.json.extensions; if (extensions) { const ext = extensions[EXTENSION_NAME]; if (ext) { if (debug) console.log(ext); const stencils = ext.stencil; if (stencils && Array.isArray(stencils)) { for (const stencil of stencils) { const obj: StencilSettingsModel = { ...stencil }; obj.compareFunc = ToThreeCompareFunction(obj.compareFunc as number); obj.passOp = ToThreeStencilOp(obj.passOp); obj.failOp = ToThreeStencilOp(obj.failOp); obj.zFailOp = ToThreeStencilOp(obj.zFailOp); if (!NEEDLE_render_objects.stencils[this.source]) NEEDLE_render_objects.stencils[this.source] = []; NEEDLE_render_objects.stencils[this.source].push(obj); } } } } return null; } } enum StencilOp { Keep = 0, Zero = 1, Replace = 2, IncrementSaturate = 3, DecrementSaturate = 4, Invert = 5, IncrementWrap = 6, DecrementWrap = 7, } enum CompareFunction { Disabled, Never, Less, Equal, LessEqual, Greater, NotEqual, GreaterEqual, Always, } function ToThreeStencilOp(op: StencilOp): ThreeStencilOp { switch (op) { case StencilOp.Keep: return KeepStencilOp; case StencilOp.Zero: return ZeroStencilOp; case StencilOp.Replace: return ReplaceStencilOp; case StencilOp.IncrementSaturate: return IncrementStencilOp; case StencilOp.DecrementSaturate: return DecrementStencilOp; case StencilOp.IncrementWrap: return IncrementWrapStencilOp; case StencilOp.DecrementWrap: return DecrementWrapStencilOp; case StencilOp.Invert: return InvertStencilOp; } return 0; } function ToThreeCompareFunction(func: CompareFunction): StencilFunc { switch (func) { case CompareFunction.Never: return NeverStencilFunc; case CompareFunction.Less: return LessStencilFunc; case CompareFunction.Equal: return EqualStencilFunc; case CompareFunction.LessEqual: return LessEqualStencilFunc; case CompareFunction.Greater: return GreaterStencilFunc; case CompareFunction.NotEqual: return NotEqualStencilFunc; case CompareFunction.GreaterEqual: return GreaterEqualStencilFunc; case CompareFunction.Always: return AlwaysStencilFunc; } return NeverStencilFunc; } export const EXTENSION_NAME = "NEEDLE_render_objects";