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.

346 lines (345 loc) • 13.3 kB
import { Color, Euler, Material, Object3D, Texture, Vector2, Vector3, Vector4 } from "three"; /** * Valid types that can be used as material property overrides */ type MaterialPropertyType = number | number[] | Color | Texture | Vector2 | Vector3 | Vector4 | null | Euler; /** * Defines offset and repeat transformations for texture coordinates */ export interface TextureTransform { /** UV offset applied to the texture */ offset?: Vector2; /** UV repeat/scale applied to the texture */ repeat?: Vector2; } /** * Represents a single material property override with optional texture transformation * @template T The type of the property value */ export interface PropertyBlockOverride<T extends MaterialPropertyType = MaterialPropertyType> { /** The name of the material property to override (e.g., "color", "map", "roughness") */ name: string; /** The value to set for this property */ value: T; /** Optional texture coordinate transformation (only used when value is a Texture) */ textureTransform?: TextureTransform; } /** * Utility type that extracts only non-function property names from a type * @template T The type to extract property names from */ type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K; }[keyof T]; /** * MaterialPropertyBlock allows per-object material property overrides without creating new material instances. * This is useful for rendering multiple objects with the same base material but different properties * (e.g., different colors, textures, or shader parameters). * * ## How Property Blocks Work * * **Important**: Overrides are registered on the **Object3D**, not on the material. * This means: * - If you change the object's material, the overrides will still be applied to the new material * - Multiple objects can share the same material but have different property overrides * - If you don't want overrides applied after changing a material, you must remove them using {@link removeOveride}, {@link clearAllOverrides}, or {@link dispose} * * The property block system works by: * - Temporarily applying overrides in onBeforeRender * - Restoring original values in onAfterRender * - Managing shader defines and program cache keys for correct shader compilation * - Supporting texture coordinate transforms per object * * ## Common Use Cases * * - **Lightmaps**: Apply unique lightmap textures to individual objects sharing the same material * - **Reflection Probes**: Apply different environment maps per object for localized reflections * - **See-through effects**: Temporarily override transparency/transmission properties for X-ray effects * * ## Getting a MaterialPropertyBlock * * **Important**: Do not use the constructor directly. Instead, use the static {@link MaterialPropertyBlock.get} method: * * ```typescript * const block = MaterialPropertyBlock.get(myMesh); * ``` * * This method will either return an existing property block or create a new one if it doesn't exist. * It automatically: * - Creates the property block instance * - Registers it in the internal registry * - Attaches the necessary render callbacks to the object * - Handles Groups by applying overrides to all child meshes * * @example Basic usage * ```typescript * // Get or create a property block for an object * const block = MaterialPropertyBlock.get(myMesh); * * // Override the color property * block.setOverride("color", new Color(1, 0, 0)); * * // Override a texture with custom UV transform (useful for lightmaps) * block.setOverride("lightMap", myLightmapTexture, { * offset: new Vector2(0.5, 0.5), * repeat: new Vector2(2, 2) * }); * * // Set a shader define * block.setDefine("USE_CUSTOM_FEATURE", 1); * ``` * * @example Material swapping behavior * ```typescript * const mesh = new Mesh(geometry, materialA); * const block = MaterialPropertyBlock.get(mesh); * block.setOverride("color", new Color(1, 0, 0)); * * // The color override is red for materialA * * // Swap the material - overrides persist and apply to the new material! * mesh.material = materialB; * // The color override is now red for materialB too * * // If you don't want overrides on the new material, remove them: * block.clearAllOverrides(); // Remove all overrides * // or * block.removeOveride("color"); // Remove specific override * // or * block.dispose(); // Remove the entire property block * ``` * * @example Lightmap usage * ```typescript * const block = MaterialPropertyBlock.get(mesh); * block.setOverride("lightMap", lightmapTexture); * block.setOverride("lightMapIntensity", 1.5); * ``` * * @example See-through effect * ```typescript * const block = MaterialPropertyBlock.get(mesh); * block.setOverride("transparent", true); * block.setOverride("opacity", 0.3); * ``` * * @template T The material type this property block is associated with */ export declare class MaterialPropertyBlock<T extends Material = Material> { private _overrides; private _defines; private _object; /** The object this property block is attached to */ get object(): Object3D | null; /** * Creates a new MaterialPropertyBlock * @param object The object this property block is for (optional) */ protected constructor(object?: Object3D | null); /** * Gets or creates a MaterialPropertyBlock for the given object. * This is the recommended way to obtain a property block instance. * * @template T The material type * @param object The object to get/create a property block for * @returns The MaterialPropertyBlock associated with this object * * @example * ```typescript * const block = MaterialPropertyBlock.get(myMesh); * block.setOverride("roughness", 0.5); * ``` */ static get<T extends Material = Material>(object: Object3D): MaterialPropertyBlock<T>; /** * Checks if an object has any property overrides * @param object The object to check * @returns True if the object has a property block with overrides */ static hasOverrides(object: Object3D): boolean; /** * Disposes this property block and cleans up associated resources. * After calling dispose, this property block should not be used. */ dispose(): void; /** * Sets or updates a material property override. * The override will be applied to the material during rendering. * * @param name The name of the material property to override (e.g., "color", "map", "roughness") * @param value The value to set * @param textureTransform Optional UV transform (only used when value is a Texture) * * @example * ```typescript * // Override a simple property * block.setOverride("roughness", 0.8); * * // Override a color * block.setOverride("color", new Color(0xff0000)); * * // Override a texture with UV transform * block.setOverride("map", texture, { * offset: new Vector2(0, 0), * repeat: new Vector2(2, 2) * }); * ``` */ setOverride<K extends NonFunctionPropertyNames<T>>(name: K, value: T[K], textureTransform?: TextureTransform): void; setOverride(name: string, value: MaterialPropertyType, textureTransform?: TextureTransform): void; /** * Gets the override for a specific property with type-safe value inference * @param name The property name to get * @returns The PropertyBlockOverride with correctly typed value if it exists, undefined otherwise * * @example * ```typescript * const block = MaterialPropertyBlock.get<MeshStandardMaterial>(mesh); * * // Value is inferred as number | undefined * const roughness = block.getOverride("roughness")?.value; * * // Value is inferred as Color | undefined * const color = block.getOverride("color")?.value; * * // Value is inferred as Texture | null | undefined * const map = block.getOverride("map")?.value; * * // Explicitly specify the type for properties not on the base material type * const transmission = block.getOverride<number>("transmission")?.value; * * // Or use a more specific material type * const physicalBlock = block as MaterialPropertyBlock<MeshPhysicalMaterial>; * const transmissionTyped = physicalBlock.getOverride("transmission")?.value; // number * ``` */ getOverride<K extends NonFunctionPropertyNames<T>>(name: K): PropertyBlockOverride<T[K] & MaterialPropertyType> | undefined; getOverride<V extends MaterialPropertyType = MaterialPropertyType>(name: string): PropertyBlockOverride<V> | undefined; /** * Removes a specific property override. * After removal, the material will use its original property value for this property. * * @param name The property name to remove the override for * * @example * ```typescript * const block = MaterialPropertyBlock.get(mesh); * * // Set some overrides * block.setOverride("color", new Color(1, 0, 0)); * block.setOverride("roughness", 0.5); * block.setOverride("lightMap", lightmapTexture); * * // Remove a specific override - the material will now use its original color * block.removeOveride("color"); * * // Other overrides (roughness, lightMap) remain active * ``` */ removeOveride<K extends NonFunctionPropertyNames<T>>(name: K | ({} & string)): void; /** * Removes all property overrides from this block. * After calling this, the material will use its original values for all properties. * * **Note**: This does NOT remove shader defines. Use {@link clearDefine} or {@link dispose} for that. * * @example Remove all overrides but keep the property block * ```typescript * const block = MaterialPropertyBlock.get(mesh); * * // Set multiple overrides * block.setOverride("color", new Color(1, 0, 0)); * block.setOverride("roughness", 0.5); * block.setOverride("lightMap", lightmapTexture); * * // Later, remove all overrides at once * block.clearAllOverrides(); * * // The material now uses its original values * // The property block still exists and can be reused with new overrides * ``` * * @example Temporarily disable all overrides * ```typescript * const block = MaterialPropertyBlock.get(mesh); * * // Save current overrides if you want to restore them later * const savedOverrides = [...block.overrides]; * * // Clear all overrides temporarily * block.clearAllOverrides(); * * // Do some rendering without overrides... * * // Restore overrides * savedOverrides.forEach(override => { * block.setOverride(override.name, override.value, override.textureTransform); * }); * ``` * * @see {@link removeOveride} - To remove a single override * @see {@link dispose} - To completely remove the property block and clean up resources */ clearAllOverrides(): void; /** * Gets all property overrides as a readonly array * @returns Array of all property overrides */ get overrides(): readonly PropertyBlockOverride[]; /** * Checks if this property block has any overrides * @returns True if there are any overrides set */ hasOverrides(): boolean; /** * Set a shader define that will be included in the program cache key. * This allows different objects sharing the same material to have different shader programs. * * Defines affect shader compilation and are useful for enabling/disabling features per-object. * * @param name The define name (e.g., "USE_LIGHTMAP", "ENABLE_REFLECTIONS") * @param value The define value (typically a boolean, number, or string) * * @example * ```typescript * // Enable a feature for this specific object * block.setDefine("USE_CUSTOM_SHADER", true); * block.setDefine("QUALITY_LEVEL", 2); * ``` */ setDefine(name: string, value: string | number | boolean): void; /** * Remove a shader define * @param name The define name to remove */ clearDefine(name: string): void; /** * Get all defines set on this property block * @returns A readonly record of all defines */ getDefines(): Readonly<Record<string, string | number | boolean>>; /** * Generates a cache key based on the current overrides and defines. * This key is used internally to ensure correct shader program selection * when objects share materials but have different property blocks. * * @returns A string representing the current state of this property block * @internal */ getCacheKey(): string; } /** * Checks if an object has a MaterialPropertyBlock attached to it. * * @param object The object to check * @returns True if the object has a property block registered * * @example * ```typescript * if (objectHasPropertyBlock(myMesh)) { * console.log("This mesh has property overrides"); * } * ``` */ export declare function objectHasPropertyBlock(object: Object3D): boolean; export {};