@hology/core 0.0.209 → 0.0.211
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/surface-definition.d.ts +3 -0
- package/dist/data/surface-definition.js +4 -0
- package/dist/effects/sequence/sequence-player.js +1 -1
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
- package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
- package/dist/gameplay/services/physics/abstract-physics-system.d.ts +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +2 -1
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/rendering.js +1 -1
- package/dist/scene/batched-mesh-2.d.ts +2 -0
- package/dist/scene/batched-mesh-2.js +1 -1
- package/dist/scene/custom-param-runtime-types.js +1 -1
- package/dist/scene/landscape/landscape-manager.d.ts +2 -1
- package/dist/scene/landscape/landscape-manager.js +1 -1
- package/dist/scene/materializer.d.ts +48 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/model.d.ts +3 -0
- package/dist/scene/scatter/painted-scatter-manager.d.ts +45 -0
- package/dist/scene/scatter/painted-scatter-manager.js +4 -0
- package/dist/scene/scatter/scatter-limits.d.ts +2 -0
- package/dist/scene/scatter/scatter-limits.js +4 -0
- package/dist/scene/scatter/surface-scatter-manager.js +1 -1
- package/dist/scene/storage/storage.js +1 -1
- package/dist/scene/surface-query.d.ts +4 -0
- package/dist/scene/surface-query.js +4 -0
- package/dist/shader/builtin/landscape-composite-shader.d.ts +1 -0
- package/dist/shader/builtin/landscape-composite-shader.js +1 -1
- package/dist/shader/builtin/standard-shader.js +1 -1
- package/dist/shader/graph/compiler.d.ts +7 -4
- package/dist/shader/graph/compiler.js +1 -1
- package/dist/shader/graph/model.d.ts +1 -1
- package/dist/shader/graph/model.js +1 -1
- package/dist/shader/graph/parameters.js +1 -1
- package/dist/shader/sprite-shader.js +1 -1
- package/dist/test/material-assignment.test.js +1 -1
- package/dist/test/materializer-prefetch.test.js +1 -1
- package/dist/test/painted-scatter-manager.test.d.ts +2 -0
- package/dist/test/painted-scatter-manager.test.js +4 -0
- package/dist/test/runtime-param-type-inference.test.js +1 -1
- package/dist/test/sequence-post-process.test.js +1 -1
- package/dist/test/shader-graph.test.js +1 -1
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Color as e,Euler as r,Material as t,Object3D as n,Texture as u,Vector2 as o,Vector3 as a,Vector4 as c,AnimationClip as l}from"three";import{BooleanNode as i,FloatNode as s,RgbNode as f,Sampler2DNode as y,Texture2dLookupNode as p,Vec2Node as m,Vec3Node as d,Vec4Node as b}from"three-shader-graph";import{getParameterDefinitionId as A,getParameterDefinitionType as g,isParameterDefinitionStructAssignableTo as S}from"../shader/parameter.js";import{Curve2 as N}from"../utils/curve.js";import{SerializedParamType as V}from"./model.js";export const applyRuntimeParamTypeInference=!0;export function convertConfiguredParamsToRuntimeTypes(e,r={}){const t={},n=[],u=Object.entries(e),o=r.applyRuntimeParamTypeInference??true;if(0===u.length||!o)return o?{params:t,skipped:n}:{params:e,skipped:n};const a=getRuntimeParameterTypeHints(e,r);for(const[e,r]of u){if(!1===r.override){t[e]=r;continue}const u=a.get(e),o=null!=u?convertConfiguredParamValueToRuntimeType(r,u):r;null!=o?t[e]=o:n.push({key:e,sourceType:r.type,targetType:u.type})}return{params:t,skipped:n}}export function getRuntimeParameterTypeHints(e,r={}){const t=new Map,n=Object.keys(e).filter(r=>!1!==e[r]?.override);if(0===n.length)return t;const u=new Set(n),o=r.parameterTarget??function(e){if(null==e)return null;try{return new e}catch{return null}}(r.parameterType),a=function(e,r){if(null!=r.propertyParams)return r.propertyParams;if(null==r.extractPropertyParameters)return[];if(null!=r.parameterType)return h(r.parameterType,r.extractPropertyParameters);if(null!=e)return h(e.constructor,r.extractPropertyParameters);return[]}(o,r);for(const e of a){if(!u.has(e.name))continue;const n=R(e,r.toSerializedParamType);null!=n&&t.set(e.name,n);const a=inferRuntimeSerializedParamTypeHint(C(o,e.name))??inferRuntimeSerializedParamTypeHint(e.options.defaultValue);null!=a&&t.set(e.name,x(a,n))}if(null!=o)for(const e of n){const r=inferRuntimeSerializedParamTypeHint(C(o,e));if(null!=r){const n=t.get(e);t.set(e,x(r,n))}}return t}const v=new WeakMap,P=new WeakMap,T=Symbol("noCachedSerializedParamType");function h(e,r){let t=v.get(r);null==t&&(t=new WeakMap,v.set(r,t));const n=t.get(e);if(null!=n)return n;const u=function(e,r){try{return r(e)}catch{return[]}}(e,r);return t.set(e,u),u}function C(e,r){return null!=e?e[r]:void 0}function R(e,r){const t=function(e,r){if(null==e||null==r)return;const t=function(e,r){const t=P.get(r);if(null==t||!t.has(e))return T;const n=t.get(e);return null===n?void 0:n}(e,r);if(t!==T)return t;try{const t=r(e);return w(e,r,t),t}catch{return void w(e,r,void 0)}}(e.type,r),n=t===V.Struct?A(e.type):void 0;return!0===e.options.array?{type:V.Array,...null!=t?{element:t}:{},...null!=n?{elementStruct:n}:{}}:null!=t?{type:t,...null!=n?{struct:n}:{}}:void 0}function w(e,r,t){let n=P.get(r);null==n&&(n=new WeakMap,P.set(r,n)),n.set(e,t??null)}function x(e,r){return e.type!==V.Array?e:{type:V.Array,element:e.element??r?.element,elementStruct:e.elementStruct??r?.elementStruct}}export function inferRuntimeSerializedParamTypeHint(g){if(null==g)return;if(Array.isArray(g)){const e=g.map(e=>inferRuntimeSerializedParamTypeHint(e)).find(e=>null!=e);return{type:V.Array,...null!=e&&e.type!==V.Array?{element:e.type}:{},...null!=e?.struct?{elementStruct:e.struct}:{}}}switch(typeof g){case"number":return{type:V.Number};case"boolean":return{type:V.Boolean};case"string":return{type:V.String}}const S=function(e){const r=e;if(e instanceof s||"function"==typeof r?.isFloat||!0===r?.isFloat)return V.FloatNode;if(e instanceof i||"function"==typeof r?.isBool||!0===r?.isBool)return V.BooleanNode;if(e instanceof f||"function"==typeof r?.isRgb||!0===r?.isRgb)return V.RgbNode;if(e instanceof m||"function"==typeof r?.isVec2||!0===r?.isVec2)return V.Vec2Node;if(e instanceof d||"function"==typeof r?.isVec3||!0===r?.isVec3)return V.Vec3Node;if(e instanceof b||"function"==typeof r?.isVec4||!0===r?.isVec4)return V.Vec4Node;if(e instanceof y||e instanceof p||"function"==typeof r?.isSampler2D||!0===r?.isSampler2D)return V.Sampler2DNode;return}(g);if(null!=S)return{type:S};if(g instanceof o)return{type:V.Vector2};if(g instanceof a)return{type:V.Vector3};if(g instanceof c)return{type:V.Vector4};if(g instanceof e)return{type:V.Color};if(g instanceof r)return{type:V.Euler};if(g instanceof u)return{type:V.Texture};if(g instanceof t)return{type:V.Material};if(g instanceof n)return{type:V.Object3D};if("undefined"!=typeof AudioBuffer&&g instanceof AudioBuffer)return{type:V.AudioBuffer};if(W(g,"VisualEffect"))return{type:V.VisualEffect};if(W(g,"PrefabOf"))return{type:V.PrefabActor};if(W(g,"Prefab"))return{type:V.Prefab};if(W(g,"DataAssetRef"))return{type:V.DataAsset};if(g instanceof N)return{type:V.Curve};if(W(g,"ColorLayer"))return{type:V.ColorLayer};if(W(g,"MaskLayer"))return{type:V.MaskLayer};if(g instanceof l)return{type:V.AnimationClip};if(W(g,"Sequence"))return{type:V.Sequence};const v=A(g.constructor);return null!=v?{type:V.Struct,struct:v}:void 0}export function convertConfiguredParamValueToRuntimeType(e,r){if(null==e||null==r)return e;if(e.type===V.Array||r.type===V.Array)return function(e,r){if(r.type===V.Array){const t=B(e),n=r.element??(t?e.element:e.type);if(null==n)return t?e:null;const u=t?e.element:e.type,o=(t?Array.isArray(e.value)?e.value:[]:[e.value]).map(e=>M(e,u,n));return o.some(e=>e===D)?null:{...e,type:V.Array,element:n,...k(e,r),value:o}}if(!B(e))return null;const t=Array.isArray(e.value)?e.value[0]:void 0;if(null==t)return null;const n=M(t,e.element,r.type);if(n===D)return null;return{...e,type:r.type,...j(e,r),value:n}}(e,r);const t=M(e.value,e.type,r.type);return t===D?null:{...e,type:r.type,...j(e,r),value:t}}function B(e){return e.type===V.Array}function j(e,r){if(r.type!==V.Struct&&e.type!==V.Struct)return{};const t=function(e,r){if(null==r)return e;if(null==e)return r;if(e===r||S(e,r))return e;const t=g(e),n=g(r);if(null==t||null==n)return e;return r}(e.type===V.Struct?e.struct:void 0,r.struct);return null!=t?{struct:t}:{}}function k(e,r){if(r.type!==V.Array)return{};const t=B(e)?e.elementStruct:void 0,n=r.elementStruct??t;return null!=n?{elementStruct:n}:{}}const D=Symbol("skippedParamValue");function M(e,r,t){if(function(e,r){if(e===r)return!0;const t=F(e);return null!=t&&t===F(r)}(r,t)&&function(e,r){switch(r){case V.Number:case V.FloatNode:return"number"==typeof e||"string"==typeof e&&Number.isFinite(parseFloat(e));case V.Boolean:case V.BooleanNode:return"boolean"==typeof e;case V.Vector2:case V.Vec2Node:return z(e,2);case V.Vector3:case V.Vec3Node:return z(e,3);case V.Vector4:case V.Vec4Node:return z(e,4);case V.Euler:return Array.isArray(e)&&e.length>=3&&null!=E(e,3);case V.Color:case V.RgbNode:return null!=q(e);case V.String:return"string"==typeof e;case V.Array:return Array.isArray(e);case V.Texture:case V.Sampler2DNode:case V.BaseActor:case V.Object3D:case V.Material:case V.AudioBuffer:case V.VisualEffect:case V.Prefab:case V.PrefabActor:case V.DataAsset:case V.AnimationClip:case V.Sequence:return null==e||"string"==typeof e||"object"==typeof e;case V.Struct:return null!=e&&"object"==typeof e&&!Array.isArray(e);case V.Curve:case V.ColorLayer:case V.MaskLayer:return null!=e}return!0}(e,t))return e;switch(t){case V.Number:case V.FloatNode:{const r=L(e);return null!=r?r:D}case V.Boolean:case V.BooleanNode:{const r=function(e){if("boolean"==typeof e)return e;if("number"==typeof e)return 0!==e;if("string"==typeof e){const r=e.trim().toLowerCase();if(["true","1","yes","on"].includes(r))return!0;if(["false","0","no","off"].includes(r))return!1}return null}(e);return null!=r?r:D}case V.Vector2:case V.Vec2Node:return H(e,2,r)??D;case V.Vector3:case V.Vec3Node:return H(e,3,r)??D;case V.Vector4:case V.Vec4Node:return H(e,4,r)??D;case V.Color:case V.RgbNode:return function(e,r){if(r===V.Color||r===V.RgbNode)return"string"==typeof e||"number"==typeof e?e:O(e);return O(e)}(e,r)??D;case V.Euler:return function(e,r){if(Array.isArray(e)){const r=E(e,3);if(null==r)return null;const t="string"==typeof e[3]?e[3]:"XYZ";return[r[0],r[1],r[2],t]}const t=H(e,3,r);return null!=t?[t[0],t[1],t[2],"XYZ"]:null}(e,r)??D;case V.String:return"string"==typeof e?e:String(e);case V.Texture:case V.Sampler2DNode:case V.BaseActor:case V.Object3D:case V.Material:case V.AudioBuffer:case V.VisualEffect:case V.Curve:case V.ColorLayer:case V.MaskLayer:case V.Prefab:case V.PrefabActor:case V.DataAsset:case V.AnimationClip:case V.Sequence:case V.Struct:return D}return D}function z(e,r){if(Array.isArray(e))return e.length>=r&&null!=E(e,r);if(null!=e&&"object"==typeof e){const t=e;return["x","y","z","w"].slice(0,r).every(e=>null!=L(t[e]))}return!1}function F(e){switch(e){case V.Number:case V.FloatNode:return"number";case V.Boolean:case V.BooleanNode:return"boolean";case V.Vector2:case V.Vec2Node:return"vector2";case V.Vector3:case V.Vec3Node:return"vector3";case V.Vector4:case V.Vec4Node:return"vector4";case V.Color:case V.RgbNode:return"color";case V.Texture:case V.Sampler2DNode:return"texture";case V.Prefab:case V.PrefabActor:return"prefab";case V.DataAsset:return"dataAsset";case V.Struct:return"struct"}}function L(e){if("number"==typeof e)return Number.isFinite(e)?e:null;if("string"==typeof e){const r=parseFloat(e);return Number.isFinite(r)?r:null}if("boolean"==typeof e)return e?1:0;if(Array.isArray(e))return L(e[0]);if(null!=e&&"object"==typeof e){const r=e;return L(r.x??r.r??r.value)}return null}function H(e,r,t){if(t===V.Color||t===V.RgbNode){const t=q(e);if(null!=t)return E(t.toArray(),r,4===r?1:0)}if(Array.isArray(e))return E(e,r);if("number"==typeof e||"string"==typeof e||"boolean"==typeof e){const t=L(e);return null!=t?new Array(r).fill(t):null}if(null!=e&&"object"==typeof e){const t=e,n=["x","y","z","w"].slice(0,r).map(e=>t[e]);if(n.some(e=>null!=e))return E(n,r);const u=["r","g","b","a"].slice(0,r).map(e=>t[e]);if(u.some(e=>null!=e))return E(u,r,4===r?1:0)}return null}function E(e,r,t=0){const n=[];for(let u=0;u<r;u++){const r=L(e[u]??t);if(null==r)return null;n.push(r)}return n}function O(e){const r=q(e);return null!=r?"#"+r.getHexString():null}function q(r){try{if(r instanceof e)return r.clone();if("string"==typeof r)return function(e){return/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(e.trim())||/^(rgb|hsl)a?\(/i.test(e.trim())}(r)?new e(r):null;if("number"==typeof r)return Number.isInteger(r)&&r>=0?new e(r):null;if(Array.isArray(r)){const t=E(r,3);return null!=t?(new e).fromArray(t):null}if(null!=r&&"object"==typeof r){const t=r;if(null!=t.r||null!=t.g||null!=t.b){const r=E([t.r,t.g,t.b],3);return null!=r?(new e).fromArray(r):null}if(null!=t.x||null!=t.y||null!=t.z){const r=E([t.x,t.y,t.z],3);return null!=r?(new e).fromArray(r):null}}}catch{return null}return null}function W(e,r){return null!=e&&"object"==typeof e&&e.constructor?.name===r}/*
|
|
1
|
+
import{Color as e,Euler as r,Material as t,Object3D as n,Texture as u,Vector2 as o,Vector3 as a,Vector4 as c,AnimationClip as l}from"three";import{BooleanNode as i,FloatNode as s,RgbNode as f,Sampler2DNode as y,Texture2dLookupNode as p,Vec2Node as m,Vec3Node as d,Vec4Node as b}from"three-shader-graph";import{getParameterDefinitionId as A,getParameterDefinitionType as g,isParameterDefinitionStructAssignableTo as S}from"../shader/parameter.js";import{Curve2 as N}from"../utils/curve.js";import{SerializedParamType as V}from"./model.js";export const applyRuntimeParamTypeInference=!0;export function convertConfiguredParamsToRuntimeTypes(e,r={}){const t={},n=[],u=Object.entries(e),o=r.applyRuntimeParamTypeInference??true;if(0===u.length||!o)return o?{params:t,skipped:n}:{params:e,skipped:n};const a=getRuntimeParameterTypeHints(e,r);for(const[e,r]of u){if(!1===r.override){t[e]=r;continue}const u=a.get(e),o=null!=u?convertConfiguredParamValueToRuntimeType(r,u):r;null!=o?t[e]=o:n.push({key:e,sourceType:r.type,targetType:u.type})}return{params:t,skipped:n}}export function getRuntimeParameterTypeHints(e,r={}){const t=new Map,n=Object.keys(e).filter(r=>!1!==e[r]?.override);if(0===n.length)return t;const u=new Set(n),o=r.parameterTarget??function(e){if(null==e)return null;try{return new e}catch{return null}}(r.parameterType),a=function(e,r){if(null!=r.propertyParams)return r.propertyParams;if(null==r.extractPropertyParameters)return[];if(null!=r.parameterType)return h(r.parameterType,r.extractPropertyParameters);if(null!=e)return h(e.constructor,r.extractPropertyParameters);return[]}(o,r);for(const e of a){if(!u.has(e.name))continue;const n=R(e,r.toSerializedParamType);null!=n&&t.set(e.name,n);const a=inferRuntimeSerializedParamTypeHint(C(o,e.name))??inferRuntimeSerializedParamTypeHint(e.options.defaultValue);null!=a&&t.set(e.name,x(a,n))}if(null!=o)for(const e of n){const r=inferRuntimeSerializedParamTypeHint(C(o,e));if(null!=r){const n=t.get(e);t.set(e,x(r,n))}}return t}const v=new WeakMap,P=new WeakMap,T=Symbol("noCachedSerializedParamType");function h(e,r){let t=v.get(r);null==t&&(t=new WeakMap,v.set(r,t));const n=t.get(e);if(null!=n)return n;const u=function(e,r){try{return r(e)}catch{return[]}}(e,r);return t.set(e,u),u}function C(e,r){return null!=e?e[r]:void 0}function R(e,r){const t=function(e,r){if(null==e||null==r)return;const t=function(e,r){const t=P.get(r);if(null==t||!t.has(e))return T;const n=t.get(e);return null===n?void 0:n}(e,r);if(t!==T)return t;try{const t=r(e);return w(e,r,t),t}catch{return void w(e,r,void 0)}}(e.type,r),n=t===V.Struct?A(e.type):void 0;return!0===e.options.array?{type:V.Array,...null!=t?{element:t}:{},...null!=n?{elementStruct:n}:{}}:null!=t?{type:t,...null!=n?{struct:n}:{}}:void 0}function w(e,r,t){let n=P.get(r);null==n&&(n=new WeakMap,P.set(r,n)),n.set(e,t??null)}function x(e,r){return e.type!==V.Array?e:{type:V.Array,element:e.element??r?.element,elementStruct:e.elementStruct??r?.elementStruct}}export function inferRuntimeSerializedParamTypeHint(g){if(null==g)return;if(Array.isArray(g)){const e=g.map(e=>inferRuntimeSerializedParamTypeHint(e)).find(e=>null!=e);return{type:V.Array,...null!=e&&e.type!==V.Array?{element:e.type}:{},...null!=e?.struct?{elementStruct:e.struct}:{}}}switch(typeof g){case"number":return{type:V.Number};case"boolean":return{type:V.Boolean};case"string":return{type:V.String}}const S=function(e){const r=e;if(e instanceof s||"function"==typeof r?.isFloat||!0===r?.isFloat)return V.FloatNode;if(e instanceof i||"function"==typeof r?.isBool||!0===r?.isBool)return V.BooleanNode;if(e instanceof f||"function"==typeof r?.isRgb||!0===r?.isRgb)return V.RgbNode;if(e instanceof m||"function"==typeof r?.isVec2||!0===r?.isVec2)return V.Vec2Node;if(e instanceof d||"function"==typeof r?.isVec3||!0===r?.isVec3)return V.Vec3Node;if(e instanceof b||"function"==typeof r?.isVec4||!0===r?.isVec4)return V.Vec4Node;if(e instanceof y||e instanceof p||"function"==typeof r?.isSampler2D||!0===r?.isSampler2D)return V.Sampler2DNode;return}(g);if(null!=S)return{type:S};if(g instanceof o)return{type:V.Vector2};if(g instanceof a)return{type:V.Vector3};if(g instanceof c)return{type:V.Vector4};if(g instanceof e)return{type:V.Color};if(g instanceof r)return{type:V.Euler};if(g instanceof u)return{type:V.Texture};if(g instanceof t)return{type:V.Material};if(g instanceof n)return{type:V.Object3D};if("undefined"!=typeof AudioBuffer&&g instanceof AudioBuffer)return{type:V.AudioBuffer};if(W(g,"VisualEffect"))return{type:V.VisualEffect};if(W(g,"PrefabOf"))return{type:V.PrefabActor};if(W(g,"Prefab"))return{type:V.Prefab};if(W(g,"DataAssetRef"))return{type:V.DataAsset};if(g instanceof N)return{type:V.Curve};if(W(g,"ColorLayer"))return{type:V.ColorLayer};if(W(g,"MaskLayer"))return{type:V.MaskLayer};if(g instanceof l)return{type:V.AnimationClip};if(W(g,"Sequence"))return{type:V.Sequence};const v=A(g.constructor);return null!=v?{type:V.Struct,struct:v}:void 0}export function convertConfiguredParamValueToRuntimeType(e,r){if(null==e||null==r)return e;if(e.type===V.Array||r.type===V.Array)return function(e,r){if(r.type===V.Array){const t=B(e),n=r.element??(t?e.element:e.type);if(null==n)return t?e:null;const u=t?e.element:e.type,o=(t?Array.isArray(e.value)?e.value:[]:[e.value]).map(e=>F(e,u,n));return o.some(e=>e===D)?null:{...e,type:V.Array,element:n,...k(e,r),value:o}}if(!B(e))return null;const t=Array.isArray(e.value)?e.value[0]:void 0;if(null==t)return null;const n=F(t,e.element,r.type);if(n===D)return null;return{...e,type:r.type,...j(e,r),value:n}}(e,r);const t=F(e.value,e.type,r.type);return t===D?null:{...e,type:r.type,...j(e,r),value:t}}function B(e){return e.type===V.Array}function j(e,r){if(r.type!==V.Struct&&e.type!==V.Struct)return{};const t=function(e,r){if(null==r)return e;if(null==e)return r;if(e===r||S(e,r))return e;const t=g(e),n=g(r);if(null==t||null==n)return e;return r}(e.type===V.Struct?e.struct:void 0,r.struct);return null!=t?{struct:t}:{}}function k(e,r){if(r.type!==V.Array)return{};const t=B(e)?e.elementStruct:void 0,n=r.elementStruct??t;return null!=n?{elementStruct:n}:{}}const D=Symbol("skippedParamValue");function F(e,r,t){if(function(e,r){if(e===r)return!0;const t=z(e);return null!=t&&t===z(r)}(r,t)&&function(e,r){switch(r){case V.Number:return"number"==typeof e||"string"==typeof e&&Number.isFinite(parseFloat(e));case V.FloatNode:return"number"==typeof e||"string"==typeof e&&Number.isFinite(parseFloat(e))||function(e){if(null==e||"object"!=typeof e||Array.isArray(e))return!1;const r=e;return"particle"===r.time&&"string"==typeof r.easing&&null!=L(r.a)&&null!=L(r.b)}(e);case V.Boolean:case V.BooleanNode:return"boolean"==typeof e;case V.Vector2:case V.Vec2Node:return M(e,2);case V.Vector3:case V.Vec3Node:return M(e,3);case V.Vector4:case V.Vec4Node:return M(e,4);case V.Euler:return Array.isArray(e)&&e.length>=3&&null!=E(e,3);case V.Color:case V.RgbNode:return null!=q(e);case V.String:return"string"==typeof e;case V.Array:return Array.isArray(e);case V.Texture:case V.Sampler2DNode:case V.BaseActor:case V.Object3D:case V.Material:case V.AudioBuffer:case V.VisualEffect:case V.Prefab:case V.PrefabActor:case V.DataAsset:case V.AnimationClip:case V.Sequence:return null==e||"string"==typeof e||"object"==typeof e;case V.Struct:return null!=e&&"object"==typeof e&&!Array.isArray(e);case V.Curve:case V.ColorLayer:case V.MaskLayer:return null!=e}return!0}(e,t))return e;switch(t){case V.Number:case V.FloatNode:{const r=L(e);return null!=r?r:D}case V.Boolean:case V.BooleanNode:{const r=function(e){if("boolean"==typeof e)return e;if("number"==typeof e)return 0!==e;if("string"==typeof e){const r=e.trim().toLowerCase();if(["true","1","yes","on"].includes(r))return!0;if(["false","0","no","off"].includes(r))return!1}return null}(e);return null!=r?r:D}case V.Vector2:case V.Vec2Node:return H(e,2,r)??D;case V.Vector3:case V.Vec3Node:return H(e,3,r)??D;case V.Vector4:case V.Vec4Node:return H(e,4,r)??D;case V.Color:case V.RgbNode:return function(e,r){if(r===V.Color||r===V.RgbNode)return"string"==typeof e||"number"==typeof e?e:O(e);return O(e)}(e,r)??D;case V.Euler:return function(e,r){if(Array.isArray(e)){const r=E(e,3);if(null==r)return null;const t="string"==typeof e[3]?e[3]:"XYZ";return[r[0],r[1],r[2],t]}const t=H(e,3,r);return null!=t?[t[0],t[1],t[2],"XYZ"]:null}(e,r)??D;case V.String:return"string"==typeof e?e:String(e);case V.Texture:case V.Sampler2DNode:case V.BaseActor:case V.Object3D:case V.Material:case V.AudioBuffer:case V.VisualEffect:case V.Curve:case V.ColorLayer:case V.MaskLayer:case V.Prefab:case V.PrefabActor:case V.DataAsset:case V.AnimationClip:case V.Sequence:case V.Struct:return D}return D}function M(e,r){if(Array.isArray(e))return e.length>=r&&null!=E(e,r);if(null!=e&&"object"==typeof e){const t=e;return["x","y","z","w"].slice(0,r).every(e=>null!=L(t[e]))}return!1}function z(e){switch(e){case V.Number:case V.FloatNode:return"number";case V.Boolean:case V.BooleanNode:return"boolean";case V.Vector2:case V.Vec2Node:return"vector2";case V.Vector3:case V.Vec3Node:return"vector3";case V.Vector4:case V.Vec4Node:return"vector4";case V.Color:case V.RgbNode:return"color";case V.Texture:case V.Sampler2DNode:return"texture";case V.Prefab:case V.PrefabActor:return"prefab";case V.DataAsset:return"dataAsset";case V.Struct:return"struct"}}function L(e){if("number"==typeof e)return Number.isFinite(e)?e:null;if("string"==typeof e){const r=parseFloat(e);return Number.isFinite(r)?r:null}if("boolean"==typeof e)return e?1:0;if(Array.isArray(e))return L(e[0]);if(null!=e&&"object"==typeof e){const r=e;return L(r.x??r.r??r.value)}return null}function H(e,r,t){if(t===V.Color||t===V.RgbNode){const t=q(e);if(null!=t)return E(t.toArray(),r,4===r?1:0)}if(Array.isArray(e))return E(e,r);if("number"==typeof e||"string"==typeof e||"boolean"==typeof e){const t=L(e);return null!=t?new Array(r).fill(t):null}if(null!=e&&"object"==typeof e){const t=e,n=["x","y","z","w"].slice(0,r).map(e=>t[e]);if(n.some(e=>null!=e))return E(n,r);const u=["r","g","b","a"].slice(0,r).map(e=>t[e]);if(u.some(e=>null!=e))return E(u,r,4===r?1:0)}return null}function E(e,r,t=0){const n=[];for(let u=0;u<r;u++){const r=L(e[u]??t);if(null==r)return null;n.push(r)}return n}function O(e){const r=q(e);return null!=r?"#"+r.getHexString():null}function q(r){try{if(r instanceof e)return r.clone();if("string"==typeof r)return function(e){return/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(e.trim())||/^(rgb|hsl)a?\(/i.test(e.trim())}(r)?new e(r):null;if("number"==typeof r)return Number.isInteger(r)&&r>=0?new e(r):null;if(Array.isArray(r)){const t=E(r,3);return null!=t?(new e).fromArray(t):null}if(null!=r&&"object"==typeof r){const t=r;if(null!=t.r||null!=t.g||null!=t.b){const r=E([t.r,t.g,t.b],3);return null!=r?(new e).fromArray(r):null}if(null!=t.x||null!=t.y||null!=t.z){const r=E([t.x,t.y,t.z],3);return null!=r?(new e).fromArray(r):null}}}catch{return null}return null}function W(e,r){return null!=e&&"object"==typeof e&&e.constructor?.name===r}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -5,7 +5,8 @@ import { SceneObject } from '../../scene/materializer.js';
|
|
|
5
5
|
import { ShaderImpl } from '../../shader/shader.js';
|
|
6
6
|
import { AssetResourceLoader } from '../asset-resource-loader.js';
|
|
7
7
|
import { LandscapeGroup } from './landscape.js';
|
|
8
|
-
|
|
8
|
+
import { grassGeometryTriangleLimit } from '../scatter/scatter-limits.js';
|
|
9
|
+
export { grassGeometryTriangleLimit };
|
|
9
10
|
/**
|
|
10
11
|
* Creates landscape with varying level of detail depending on the
|
|
11
12
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Subject as e,debounceTime as t}from"rxjs";import{Box3 as s,BufferAttribute as n,InstancedMesh as o,MathUtils as a,Matrix4 as i,Mesh as r,MeshStandardMaterial as c,PerspectiveCamera as l,PlaneGeometry as h,ShaderMaterial as u,Triangle as f,Vector2 as m,Vector3 as p}from"three";import{materialFromAsset as d}from"../../scene/materializer.js";import{getMaterialAttribute as w}from"../../scene/materials/utils/material-painting";import{whenIdle as g}from"../../utils/async.js";import{indexBy as y}from"../../utils/collections.js";import{meanVectors3withWeight as M}from"../../utils/math.js";import{LandscapeMesh as b,defaultLandscapeMaterial as x}from"./landscape.js";import{smoothNormalsCrossMeshes as S}from"./utils.js";export const grassGeometryTriangleLimit=400;new p,new p;const A=new p,v=new p,z=new p;export class LandscapeManager{constructor(s,n,o,a,r,c,l){this.view=n,this.landscape=o,this.assetManagerService=a,this.assetService=r,this.shaders=c,this.applyMaterial=l,this.scatterMeshes=new Map,this.loadedScatterSquares=new Set,this.refreshRequests=new e,this.defaultLandscapeMaterial=x.clone(),this.scatterMeshPool=[],this.onLoopHandler=()=>this.update(),this.sectionCache=new Map,this._matrix=new i,this.scatterGeometryCache=new Map,this._lastUpdatePosition=new p,this._cameraPosition=new p,this.source=JSON.parse(JSON.stringify(s)),this.view.onLoop(this.onLoopHandler),this.defaultLandscapeMaterial.name=x.name,this.defaultLandscapeMaterial.color=x.color,this.refreshRequests.pipe(t(500)).subscribe(e=>this.refreshScatter(e.origin,e.force,e.predicate))}updateSource(e){this.source=JSON.parse(JSON.stringify(e))}updateShaders(e){this.shaders=e}async loadGrass(){const e=await this.assetService.getAsset("6ij937n72g");await this.assetManagerService.getMesh(e);this.grassGeometry=new h(2,2,3,3);const t=this.grassGeometry.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.needsUpdate=!0,this.grassMaterial=new c({color:3765785})}refreshGeometry(){const e=this.source.landscape.options,t=(new p,new p);this.view.camera.getWorldPosition(t);const s=[];z.fromArray(this.source.position);const n=this.view.camera instanceof l?Math.min(this.view.camera.far,1e3):1e3,o=1.1*n,a=e.sections.y*e.sectionSize/-2,i=e.sections.x*e.sectionSize/-2;for(let r=0;r<e.sections.x;r++)if(A.x=i+r*e.sectionSize,!(Math.abs(t.x-A.x)>o))for(let c=0;c<e.sections.y;c++){A.z=a+c*e.sectionSize,v.copy(z).add(A);const l=v.distanceTo(t),h=`${r},${c}`,u=this.landscape.sections.find(e=>e.x===r&&e.y===c);if(l<=n){if(null==u){this.sectionCache.has(h)||this.sectionCache.set(h,this.createLandscapeMesh(this.source,e,i,a,r,c));const t=this.sectionCache.get(h);this.applyMaterial(t),this.landscape.add(t),s.push(t)}}else l>o&&this.landscape.remove(u)}S(s)}applyHeightMap(e,t,s,n=1){const o=Math.pow(s+1,2),a=e.getAttribute("position");if(1===n)for(const e of t.points)a.setY(e.i,e.y);else{const e=y(t.points??[],e=>e.i);for(let t=0;t<a.count;t++){const s=P(t,a.count,o);let n=0;n=s%1==0?e.get(s)?.y??0:Math.floor(e.get(s)?.y),a.setY(t,n)}}a.needsUpdate=!0,e.computeVertexNormals()}deleteOldScatterMeshes(){const e=new Set;for(const[t,s]of this.source.grass?.layers.entries()??[])for(const[n,o]of s.meshes.entries()){const s=`${t}-${n}`;e.add(s)}for(const t of this.scatterMeshes.keys())if(!e.has(t)){this.scatterMeshes.get(t).forEach(e=>{e.parent?.remove(e),e.dispose()}),this.scatterMeshes.delete(t)}}queueRefreshScatter(e,t=!1,s=()=>!0){this.refreshRequests.next({origin:e,force:t,predicate:s})}async refreshScatter(e,t=!1,s=()=>!0){t&&this.scatterGeometryCache.clear(),this.deleteOldScatterMeshes();for(const[n,i]of this.source.grass?.layers.entries()??[])for(const[c,l]of i.meshes.entries()){const i=`${n}-${c}`;this.scatterMeshes.has(i)||this.scatterMeshes.set(i,new Map);const h=this.scatterMeshes.get(i),u=await this.assetService.getAsset(l.assetId);if(null==u){console.error(`Can not find asset with id ${l.assetId}`);continue}let m;try{m=await this.assetManagerService.getMesh(u)}catch(e){console.error(`Failed to load mesh in landscape manager for asset with name ${u.name}`,e);continue}const w=[];if(m.scene.traverse(e=>{e instanceof r&&w.push(e)}),1!==w.length){console.log(m),console.warn("Dynamic grass only works for meshes with a single geometry.");continue}if(!(w[0]instanceof r)){console.warn("Only meshes can be used for dynamic grass. Found:",m.scene);continue}const b=w[0];let x=b.geometry;if(this.scatterGeometryCache.has(b.geometry.uuid))x=this.scatterGeometryCache.get(b.geometry.uuid);else if(x=b.geometry.clone(),!0===l.normalsUp&&F(x),this.scatterGeometryCache.set(b.geometry.uuid,x),null==x.userData.updatedMatrix){m.scene.updateMatrixWorld(),x.applyMatrix4(b.matrixWorld),x.userData.updatedMatrix=!0;const e=u.mesh?.rescale??1;1!==e&&x.scale(e,e,e)}const S=x.getIndex()??x.getAttribute("position"),A=null!=S?S.count/3:0;if(A>400){console.warn(`The triangle count of ${u.name} is too big ${A}. Keep it below 400`);continue}const v=null!=u.materialAssignments&&u.materialAssignments.length>0?u.materialAssignments[0].materialId:null,z=null!=v&&"null"!==v?await d(await this.assetService.getAsset(v),null,this.assetService,this.assetManagerService,this.shaders,!1):null;let P=null!=z?z:b.material;if(Array.isArray(P))for(const e of P)e.userData.disableAO=!0;else P.userData.disableAO=!0;const L=a.degToRad(l.maxSlope??90),T=Math.cos(L),_=this.landscape.sections,D=_.filter($(e,l.viewDistance)),q=D.filter(e=>!h.has(e.uuid)||t).filter(e=>s(e));_.filter(O(e,2*l.viewDistance)).forEach(e=>{const t=h.get(e.uuid);null!=t&&(t.visible=!1)});for(const e of D){const t=h.get(e.uuid);null!=t&&(t.visible=!0)}performance.now();const I=this.source.landscape.options,U=I.sectionSize,k=l.density??1??1,H=I.density,X=U/H,Y=k,E=X/Math.sqrt(Y),J=Math.pow(H,2),Z=E/X,K=Math.floor(J*Y),V=[0,0,0];for(const e of q)await g(async()=>{e.updateWorldMatrix(!0,!1);const s=this._matrix,a=new p,i=e.geometry.getAttribute("position"),r=e.geometry.getAttribute("normal"),c=(this.source.vertexMaterials??[]).filter(t=>t.m===e.name),u=y(c,e=>e.i);let m=h.get(e.uuid);if(null==m||m.count==K&&!t||(m.parent?.remove(m),this.scatterMeshPool.push(m),h.delete(e.uuid),m=null),null==m){const e=this.scatterMeshPool.findIndex(e=>e.count>=K);e>-1?(m=this.scatterMeshPool[e],m.geometry=x,m.material=P,this.scatterMeshPool.splice(e,1)):m=new o(x,P,K),m.raycast=()=>{},m.receiveShadow=!0}m.visible=!0;const d=new f(new p,new p,new p);let[w,g,b,S]=[new p,new p,new p,new p],[A,v,z]=[[],[],[]],[L,O,$]=[new p,new p,new p,new p];const _=new p,D=new p,q=new p,I=new p,U=new f(new p,new p,new p),k=new f(new p,new p,new p),F=new f(new p,new p,new p),X=new f(new p,new p,new p);let E=0;e:for(let t=0;t<J;t++){const o=Math.floor(t/H);w.fromBufferAttribute(i,t+o),I.copy(w).applyMatrix4(e.matrixWorld),U.a.copy(w),U.b.fromBufferAttribute(i,t+1+o),U.c.fromBufferAttribute(i,t+H+1+o),k.a.copy(U.b),k.b.copy(U.c),k.c.fromBufferAttribute(i,t+H+2+o),F.a.fromBufferAttribute(r,t+o),F.b.fromBufferAttribute(r,t+1+o),F.c.fromBufferAttribute(r,t+H+1+o),X.a.copy(F.b),X.b.copy(F.c),X.c.fromBufferAttribute(r,t+H+2+o);const c=[];c[0]=u.get(t+o)?.w,c[1]=u.get(t+1+o)?.w,c[2]=u.get(t+H+1+o)?.w,c[3]=u.get(t+H+2+o)?.w;let h=0;for(let e=0;e<=1+Z;e+=Z)for(let t=0;t<=1+Z;t+=Z){if(E>K)break e;if(h++,h>Y)continue e;1-e>t?(g=U.a,b=U.b,S=U.c,L=F.a,O=F.b,$=F.c,A=c[0],v=c[1],z=c[2]):(g=k.a,b=k.b,S=k.c,L=X.a,O=X.b,$=X.c,A=c[1],v=c[2],z=c[3]),d.a.copy(g),d.b.copy(b),d.c.copy(S),C(d),_.set(w.x,0,w.z),R(d,_),d.getBarycoord(_,a).toArray(V),G[0]=A,G[1]=v,G[2]=z;if(j(G,V,.2)!==n-1)continue;if(M([g,b,S],V,D),M([L,O,$],V,q),null!=l.maxSlope&&l.maxSlope<90&&q.y<T)continue;const o=D;o.y+=B(l.offsetMin,l.offsetMax);const i=B(l.scaleMin??1,l.scaleMax??1);s.makeScale(i,i,i);const r=s.elements;r[12]=o.x,r[13]=o.y,r[14]=o.z,!1!==l.randomRotation&&N(s,i),l.alignToNormal&&W(s,o,m.matrixWorld,q);const u=m.instanceMatrix.array,f=16*E;u[f]=r[0],u[f+1]=r[1],u[f+2]=r[2],u[f+3]=r[3],u[f+4]=r[4],u[f+5]=r[5],u[f+6]=r[6],u[f+7]=r[7],u[f+8]=r[8],u[f+9]=r[9],u[f+10]=r[10],u[f+11]=r[11],u[f+12]=r[12],u[f+13]=r[13],u[f+14]=r[14],u[f+15]=r[15],E++}}m.count=E,m.instanceMatrix.needsUpdate=!0,m.position.copy(e.position),m.updateMatrix(),h.has(e.uuid)||this.landscape?.add(m),h.set(e.uuid,m),m.userData.meshConfig=l});performance.now()}}stop(){this.view.removeOnLoop(this.onLoopHandler)}update(){this.view.camera&&(this.view.camera.getWorldPosition(this._cameraPosition),this._cameraPosition.distanceTo(this._lastUpdatePosition)>10&&(this._lastUpdatePosition.copy(this._cameraPosition),this.refreshGeometry(),this.refreshScatter(this._cameraPosition)))}clear(){this.scatterMeshes.forEach(e=>e.forEach(e=>e.parent?.remove(e)))}createLandscapeMesh(e,t,s,n,o,a){const i=new h(t.sectionSize,t.sectionSize,t.density,t.density);i.rotateX(Math.PI/-2);const r=this.defaultLandscapeMaterial,c=new b(i,r);c.position.x=s+o*t.sectionSize,c.position.z=n+a*t.sectionSize,c.receiveShadow=!0,c.castShadow=!1,c.userData.landscape={x:o,y:a},c.x=o,c.y=a,c.name=`${o},${a}`,w(c,0,!0),w(c,4,!0);const l=e.landscape.heightMaps.find(e=>e.x===o&&e.y===a);if(null!=l&&this.applyHeightMap(i,l,t.density,1),i.computeTangents(),i.computeBoundsTree(),null!=e.landscape.holes&&e.landscape.holes.length>0){const t=getHoleAttribute(c,!0);for(const s of e.landscape.holes)s.m===c.name&&t.setX(s.i,s.w[0])}return c}}export function getHoleAttribute(e,t=!1){if(!e.geometry.hasAttribute("hole")||t){const t=new Float32Array(e.geometry.getAttribute("position").array.length);e.geometry.setAttribute("hole",new n(t,1))}return e.geometry.getAttribute("hole")}function P(e,t,s){const n=Math.sqrt(t),o=Math.floor(e/n)/(n-1),a=e%n/(n-1),i=Math.sqrt(s);return(s-1)*o-(i-1)*o+(i-1)*a}new Map,new m(0,0),new m(1,0),new m(0,1),new m(1,0),new m(0,1),new m(1,1),new p;const L=new s;function O(e,t){return function(s){return L.setFromObject(s).distanceToPoint(e)>t}}function $(e,t){return function(s){return L.setFromObject(s).distanceToPoint(e)<t}}function C(e){e.a.y=0,e.b.y=0,e.c.y=0}const G=[];function j(e,t,s=.5){const n=G;let o=-1,a=-1;for(let e=0;e<n.length;e++)if(null!=n[e])for(let i=0;i<n[e].length;i++){const r=n[e][i]*t[e];r>s&&r>a&&(a=r,o=i)}return o}function B(e,t){let s=t-e,n=D();return n*=s,n+=e,n}const T=[];let _=1e3;for(;_--;)T.push(Math.random());function D(){return++_>=T.length?T[_=0]:T[_]}const q=[];let I=20;for(;I--;)q.push((new i).makeRotationY(D()*Math.PI/2));function R(e,t){let s=D(),n=D();s+n>1&&(s=1-s,n=1-n);const o=e.a,a=e.b,i=e.c;t.x=o.x+s*(a.x-o.x)+n*(i.x-o.x),t.z=o.z+s*(a.z-o.z)+n*(i.z-o.z)}new p;new p;const U=new p,k=new p(0,1,0),H=(new i).makeRotationX(Math.PI/-2);function W(e,t,s,n){e.lookAt(U,n,k).multiply(H)}new i;function N(e,t=1){const s=(++I>=q.length?q[I=0]:q[I]).elements,n=e.elements;n[0]=s[0]*t,n[4]=s[4]*t,n[8]=s[8]*t,n[1]=s[1]*t,n[5]=s[5]*t,n[9]=s[9]*t,n[2]=s[2]*t,n[6]=s[6]*t,n[10]=s[10]*t}function F(e){const t=e.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.normalized=!0,t.needsUpdate=!0}/*
|
|
1
|
+
import{Subject as e,debounceTime as t}from"rxjs";import{Box3 as s,BufferAttribute as n,InstancedMesh as o,MathUtils as a,Matrix4 as i,Mesh as r,MeshStandardMaterial as c,PerspectiveCamera as l,PlaneGeometry as h,ShaderMaterial as u,Triangle as f,Vector2 as m,Vector3 as p}from"three";import{materialFromAsset as d}from"../../scene/materializer.js";import{getMaterialAttribute as w}from"../../scene/materials/utils/material-painting";import{whenIdle as g}from"../../utils/async.js";import{indexBy as y}from"../../utils/collections.js";import{meanVectors3withWeight as M}from"../../utils/math.js";import{LandscapeMesh as b,defaultLandscapeMaterial as x}from"./landscape.js";import{smoothNormalsCrossMeshes as S}from"./utils.js";import{grassGeometryTriangleLimit as A}from"../scatter/scatter-limits.js";export{A as grassGeometryTriangleLimit};new p,new p;const v=new p,z=new p,P=new p;export class LandscapeManager{constructor(s,n,o,a,r,c,l){this.view=n,this.landscape=o,this.assetManagerService=a,this.assetService=r,this.shaders=c,this.applyMaterial=l,this.scatterMeshes=new Map,this.loadedScatterSquares=new Set,this.refreshRequests=new e,this.defaultLandscapeMaterial=x.clone(),this.scatterMeshPool=[],this.onLoopHandler=()=>this.update(),this.sectionCache=new Map,this._matrix=new i,this.scatterGeometryCache=new Map,this._lastUpdatePosition=new p,this._cameraPosition=new p,this.source=JSON.parse(JSON.stringify(s)),this.view.onLoop(this.onLoopHandler),this.defaultLandscapeMaterial.name=x.name,this.defaultLandscapeMaterial.color=x.color,this.refreshRequests.pipe(t(500)).subscribe(e=>this.refreshScatter(e.origin,e.force,e.predicate))}updateSource(e){this.source=JSON.parse(JSON.stringify(e))}updateShaders(e){this.shaders=e}async loadGrass(){const e=await this.assetService.getAsset("6ij937n72g");await this.assetManagerService.getMesh(e);this.grassGeometry=new h(2,2,3,3);const t=this.grassGeometry.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.needsUpdate=!0,this.grassMaterial=new c({color:3765785})}refreshGeometry(){const e=this.source.landscape.options,t=(new p,new p);this.view.camera.getWorldPosition(t);const s=[];P.fromArray(this.source.position);const n=this.view.camera instanceof l?Math.min(this.view.camera.far,1e3):1e3,o=1.1*n,a=e.sections.y*e.sectionSize/-2,i=e.sections.x*e.sectionSize/-2;for(let r=0;r<e.sections.x;r++)if(v.x=i+r*e.sectionSize,!(Math.abs(t.x-v.x)>o))for(let c=0;c<e.sections.y;c++){v.z=a+c*e.sectionSize,z.copy(P).add(v);const l=z.distanceTo(t),h=`${r},${c}`,u=this.landscape.sections.find(e=>e.x===r&&e.y===c);if(l<=n){if(null==u){this.sectionCache.has(h)||this.sectionCache.set(h,this.createLandscapeMesh(this.source,e,i,a,r,c));const t=this.sectionCache.get(h);this.applyMaterial(t),this.landscape.add(t),s.push(t)}}else l>o&&this.landscape.remove(u)}S(s)}applyHeightMap(e,t,s,n=1){const o=Math.pow(s+1,2),a=e.getAttribute("position");if(1===n)for(const e of t.points)a.setY(e.i,e.y);else{const e=y(t.points??[],e=>e.i);for(let t=0;t<a.count;t++){const s=$(t,a.count,o);let n=0;n=s%1==0?e.get(s)?.y??0:Math.floor(e.get(s)?.y),a.setY(t,n)}}a.needsUpdate=!0,e.computeVertexNormals()}deleteOldScatterMeshes(){const e=new Set;for(const[t,s]of this.source.grass?.layers.entries()??[])for(const[n,o]of s.meshes.entries()){const s=`${t}-${n}`;e.add(s)}for(const t of this.scatterMeshes.keys())if(!e.has(t)){this.scatterMeshes.get(t).forEach(e=>{e.parent?.remove(e),e.dispose()}),this.scatterMeshes.delete(t)}}queueRefreshScatter(e,t=!1,s=()=>!0){this.refreshRequests.next({origin:e,force:t,predicate:s})}async refreshScatter(e,t=!1,s=()=>!0){t&&this.scatterGeometryCache.clear(),this.deleteOldScatterMeshes();for(const[n,i]of this.source.grass?.layers.entries()??[])for(const[c,l]of i.meshes.entries()){const i=`${n}-${c}`;this.scatterMeshes.has(i)||this.scatterMeshes.set(i,new Map);const h=this.scatterMeshes.get(i),u=await this.assetService.getAsset(l.assetId);if(null==u){console.error(`Can not find asset with id ${l.assetId}`);continue}let m;try{m=await this.assetManagerService.getMesh(u)}catch(e){console.error(`Failed to load mesh in landscape manager for asset with name ${u.name}`,e);continue}const w=[];if(m.scene.traverse(e=>{e instanceof r&&w.push(e)}),1!==w.length){console.log(m),console.warn("Dynamic grass only works for meshes with a single geometry.");continue}if(!(w[0]instanceof r)){console.warn("Only meshes can be used for dynamic grass. Found:",m.scene);continue}const b=w[0];let x=b.geometry;if(this.scatterGeometryCache.has(b.geometry.uuid))x=this.scatterGeometryCache.get(b.geometry.uuid);else if(x=b.geometry.clone(),!0===l.normalsUp&&X(x),this.scatterGeometryCache.set(b.geometry.uuid,x),null==x.userData.updatedMatrix){m.scene.updateMatrixWorld(),x.applyMatrix4(b.matrixWorld),x.userData.updatedMatrix=!0;const e=u.mesh?.rescale??1;1!==e&&x.scale(e,e,e)}const S=x.getIndex()??x.getAttribute("position"),v=null!=S?S.count/3:0;if(v>A){console.warn(`The triangle count of ${u.name} is too big ${v}. Keep it below ${A}`);continue}const z=null!=u.materialAssignments&&u.materialAssignments.length>0?u.materialAssignments[0].materialId:null,P=null!=z&&"null"!==z?await d(await this.assetService.getAsset(z),null,this.assetService,this.assetManagerService,this.shaders,!1):null;let $=null!=P?P:b.material;if(Array.isArray($))for(const e of $)e.userData.disableAO=!0;else $.userData.disableAO=!0;const L=a.degToRad(l.maxSlope??90),D=Math.cos(L),T=this.landscape.sections,q=T.filter(j(e,l.viewDistance)),I=q.filter(e=>!h.has(e.uuid)||t).filter(e=>s(e));T.filter(O(e,2*l.viewDistance)).forEach(e=>{const t=h.get(e.uuid);null!=t&&(t.visible=!1)});for(const e of q){const t=h.get(e.uuid);null!=t&&(t.visible=!0)}performance.now();const R=this.source.landscape.options,k=R.sectionSize,H=l.density??1??1,W=R.density,Y=k/W,E=H,J=Y/Math.sqrt(E),Z=Math.pow(W,2),K=J/Y,V=Math.floor(Z*E),Q=[0,0,0];for(const e of I)await g(async()=>{e.updateWorldMatrix(!0,!1);const s=this._matrix,a=new p,i=e.geometry.getAttribute("position"),r=e.geometry.getAttribute("normal"),c=(this.source.vertexMaterials??[]).filter(t=>t.m===e.name),u=y(c,e=>e.i);let m=h.get(e.uuid);if(null==m||m.count==V&&!t||(m.parent?.remove(m),this.scatterMeshPool.push(m),h.delete(e.uuid),m=null),null==m){const e=this.scatterMeshPool.findIndex(e=>e.count>=V);e>-1?(m=this.scatterMeshPool[e],m.geometry=x,m.material=$,this.scatterMeshPool.splice(e,1)):m=new o(x,$,V),m.raycast=()=>{},m.receiveShadow=!0}m.visible=!0;const d=new f(new p,new p,new p);let[w,g,b,S]=[new p,new p,new p,new p],[A,v,z]=[[],[],[]],[P,L,O]=[new p,new p,new p,new p];const j=new p,T=new p,q=new p,I=new p,R=new f(new p,new p,new p),k=new f(new p,new p,new p),H=new f(new p,new p,new p),X=new f(new p,new p,new p);let Y=0;e:for(let t=0;t<Z;t++){const o=Math.floor(t/W);w.fromBufferAttribute(i,t+o),I.copy(w).applyMatrix4(e.matrixWorld),R.a.copy(w),R.b.fromBufferAttribute(i,t+1+o),R.c.fromBufferAttribute(i,t+W+1+o),k.a.copy(R.b),k.b.copy(R.c),k.c.fromBufferAttribute(i,t+W+2+o),H.a.fromBufferAttribute(r,t+o),H.b.fromBufferAttribute(r,t+1+o),H.c.fromBufferAttribute(r,t+W+1+o),X.a.copy(H.b),X.b.copy(H.c),X.c.fromBufferAttribute(r,t+W+2+o);const c=[];c[0]=u.get(t+o)?.w,c[1]=u.get(t+1+o)?.w,c[2]=u.get(t+W+1+o)?.w,c[3]=u.get(t+W+2+o)?.w;let h=0;for(let e=0;e<=1+K;e+=K)for(let t=0;t<=1+K;t+=K){if(Y>V)break e;if(h++,h>E)continue e;1-e>t?(g=R.a,b=R.b,S=R.c,P=H.a,L=H.b,O=H.c,A=c[0],v=c[1],z=c[2]):(g=k.a,b=k.b,S=k.c,P=X.a,L=X.b,O=X.c,A=c[1],v=c[2],z=c[3]),d.a.copy(g),d.b.copy(b),d.c.copy(S),C(d),j.set(w.x,0,w.z),U(d,j),d.getBarycoord(j,a).toArray(Q),B[0]=A,B[1]=v,B[2]=z;if(G(B,Q,.2)!==n-1)continue;if(M([g,b,S],Q,T),M([P,L,O],Q,q),null!=l.maxSlope&&l.maxSlope<90&&q.y<D)continue;const o=T;o.y+=_(l.offsetMin,l.offsetMax);const i=_(l.scaleMin??1,l.scaleMax??1);s.makeScale(i,i,i);const r=s.elements;r[12]=o.x,r[13]=o.y,r[14]=o.z,!1!==l.randomRotation&&F(s,i),l.alignToNormal&&N(s,o,m.matrixWorld,q);const u=m.instanceMatrix.array,f=16*Y;u[f]=r[0],u[f+1]=r[1],u[f+2]=r[2],u[f+3]=r[3],u[f+4]=r[4],u[f+5]=r[5],u[f+6]=r[6],u[f+7]=r[7],u[f+8]=r[8],u[f+9]=r[9],u[f+10]=r[10],u[f+11]=r[11],u[f+12]=r[12],u[f+13]=r[13],u[f+14]=r[14],u[f+15]=r[15],Y++}}m.count=Y,m.instanceMatrix.needsUpdate=!0,m.position.copy(e.position),m.updateMatrix(),h.has(e.uuid)||this.landscape?.add(m),h.set(e.uuid,m),m.userData.meshConfig=l});performance.now()}}stop(){this.view.removeOnLoop(this.onLoopHandler)}update(){this.view.camera&&(this.view.camera.getWorldPosition(this._cameraPosition),this._cameraPosition.distanceTo(this._lastUpdatePosition)>10&&(this._lastUpdatePosition.copy(this._cameraPosition),this.refreshGeometry(),this.refreshScatter(this._cameraPosition)))}clear(){this.scatterMeshes.forEach(e=>e.forEach(e=>e.parent?.remove(e)))}createLandscapeMesh(e,t,s,n,o,a){const i=new h(t.sectionSize,t.sectionSize,t.density,t.density);i.rotateX(Math.PI/-2);const r=this.defaultLandscapeMaterial,c=new b(i,r);c.position.x=s+o*t.sectionSize,c.position.z=n+a*t.sectionSize,c.receiveShadow=!0,c.castShadow=!1,c.userData.landscape={x:o,y:a},c.x=o,c.y=a,c.name=`${o},${a}`,w(c,0,!0),w(c,4,!0);const l=e.landscape.heightMaps.find(e=>e.x===o&&e.y===a);if(null!=l&&this.applyHeightMap(i,l,t.density,1),i.computeTangents(),i.computeBoundsTree(),null!=e.landscape.holes&&e.landscape.holes.length>0){const t=getHoleAttribute(c,!0);for(const s of e.landscape.holes)s.m===c.name&&t.setX(s.i,s.w[0])}return c}}export function getHoleAttribute(e,t=!1){if(!e.geometry.hasAttribute("hole")||t){const t=new Float32Array(e.geometry.getAttribute("position").array.length);e.geometry.setAttribute("hole",new n(t,1))}return e.geometry.getAttribute("hole")}function $(e,t,s){const n=Math.sqrt(t),o=Math.floor(e/n)/(n-1),a=e%n/(n-1),i=Math.sqrt(s);return(s-1)*o-(i-1)*o+(i-1)*a}new Map,new m(0,0),new m(1,0),new m(0,1),new m(1,0),new m(0,1),new m(1,1),new p;const L=new s;function O(e,t){return function(s){return L.setFromObject(s).distanceToPoint(e)>t}}function j(e,t){return function(s){return L.setFromObject(s).distanceToPoint(e)<t}}function C(e){e.a.y=0,e.b.y=0,e.c.y=0}const B=[];function G(e,t,s=.5){const n=B;let o=-1,a=-1;for(let e=0;e<n.length;e++)if(null!=n[e])for(let i=0;i<n[e].length;i++){const r=n[e][i]*t[e];r>s&&r>a&&(a=r,o=i)}return o}function _(e,t){let s=t-e,n=q();return n*=s,n+=e,n}const D=[];let T=1e3;for(;T--;)D.push(Math.random());function q(){return++T>=D.length?D[T=0]:D[T]}const I=[];let R=20;for(;R--;)I.push((new i).makeRotationY(q()*Math.PI/2));function U(e,t){let s=q(),n=q();s+n>1&&(s=1-s,n=1-n);const o=e.a,a=e.b,i=e.c;t.x=o.x+s*(a.x-o.x)+n*(i.x-o.x),t.z=o.z+s*(a.z-o.z)+n*(i.z-o.z)}new p;new p;const k=new p,H=new p(0,1,0),W=(new i).makeRotationX(Math.PI/-2);function N(e,t,s,n){e.lookAt(k,n,H).multiply(W)}new i;function F(e,t=1){const s=(++R>=I.length?I[R=0]:I[R]).elements,n=e.elements;n[0]=s[0]*t,n[4]=s[4]*t,n[8]=s[8]*t,n[1]=s[1]*t,n[5]=s[5]*t,n[9]=s[9]*t,n[2]=s[2]*t,n[6]=s[6]*t,n[10]=s[10]*t}function X(e){const t=e.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.normalized=!0,t.needsUpdate=!0}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -20,7 +20,7 @@ import type { ShaderGraphDocument } from "../shader/graph/model.js";
|
|
|
20
20
|
export { applyRuntimeParamTypeInference, convertConfiguredParamsToRuntimeTypes, convertConfiguredParamValueToRuntimeType, inferRuntimeSerializedParamTypeHint };
|
|
21
21
|
export type { RuntimeSerializedParamTypeHint } from './custom-param-runtime-types.js';
|
|
22
22
|
export type SceneObjectId = string;
|
|
23
|
-
export type SceneObjectType = "asset_mesh" | "light" | "shape_mesh" | "spline" | "landscape" | "particles" | "global_fog" | "global_light" | "actor" | "group" | "prefab" | "vfx" | "sky" | "world_env";
|
|
23
|
+
export type SceneObjectType = "asset_mesh" | "light" | "shape_mesh" | "spline" | "landscape" | "particles" | "global_fog" | "global_light" | "actor" | "group" | "prefab" | "vfx" | "sky" | "world_env" | "scatter";
|
|
24
24
|
export type LightType = "point" | "spot" | "directional" | "ambient" | "rectArea";
|
|
25
25
|
type DirectionalLightSettings = {
|
|
26
26
|
color: string;
|
|
@@ -122,6 +122,10 @@ export type SceneObject = {
|
|
|
122
122
|
* Surface scatter settings. Only relevant for object type asset_mesh in v1.
|
|
123
123
|
*/
|
|
124
124
|
surfaceScatter?: SurfaceScatterSettings;
|
|
125
|
+
/**
|
|
126
|
+
* Artist-painted scatter settings. Only relevant for object type scatter.
|
|
127
|
+
*/
|
|
128
|
+
paintedScatter?: PaintedScatterSettings;
|
|
125
129
|
/**
|
|
126
130
|
* Actor settings. Only relevant for object type actor
|
|
127
131
|
*/
|
|
@@ -233,6 +237,39 @@ export interface ScatterMeshSettings {
|
|
|
233
237
|
}
|
|
234
238
|
export interface GrassMesh extends ScatterMeshSettings {
|
|
235
239
|
}
|
|
240
|
+
export type PaintedScatterEraseMode = 'enabled' | 'all';
|
|
241
|
+
export interface PaintedScatterSettings {
|
|
242
|
+
palette: PaintedScatterPaletteItem[];
|
|
243
|
+
brush: PaintedScatterBrushSettings;
|
|
244
|
+
cells: PaintedScatterCell[];
|
|
245
|
+
}
|
|
246
|
+
export interface PaintedScatterPaletteItem extends ScatterMeshSettings {
|
|
247
|
+
id: string;
|
|
248
|
+
enabled: boolean;
|
|
249
|
+
weight: number;
|
|
250
|
+
minSpacing: number;
|
|
251
|
+
castShadow: boolean;
|
|
252
|
+
receiveShadow: boolean;
|
|
253
|
+
}
|
|
254
|
+
export interface PaintedScatterBrushSettings {
|
|
255
|
+
radius: number;
|
|
256
|
+
density: number;
|
|
257
|
+
eraseMode?: PaintedScatterEraseMode;
|
|
258
|
+
}
|
|
259
|
+
export interface PaintedScatterCell {
|
|
260
|
+
key: string;
|
|
261
|
+
revision: number;
|
|
262
|
+
instances: PaintedScatterInstance[];
|
|
263
|
+
}
|
|
264
|
+
export interface PaintedScatterInstance {
|
|
265
|
+
id: string;
|
|
266
|
+
paletteId: string;
|
|
267
|
+
position: number[];
|
|
268
|
+
rotation: [number, number, number, THREE.EulerOrder?];
|
|
269
|
+
scale: number[];
|
|
270
|
+
surfaceNormal?: number[];
|
|
271
|
+
sourceObjectId?: string;
|
|
272
|
+
}
|
|
236
273
|
export type FogType = "linear" | "density";
|
|
237
274
|
export interface FogSettings {
|
|
238
275
|
type: FogType;
|
|
@@ -309,6 +346,7 @@ export declare class SceneMaterializer {
|
|
|
309
346
|
private createAssetSubscription;
|
|
310
347
|
readonly components: GameComponent[];
|
|
311
348
|
private readonly landscapeManagers;
|
|
349
|
+
private paintedScatterManager?;
|
|
312
350
|
private surfaceScatterManager?;
|
|
313
351
|
materializedActors: Map<string, BaseActor>;
|
|
314
352
|
idToSceneObject: Map<string, SceneObject>;
|
|
@@ -344,6 +382,8 @@ export declare class SceneMaterializer {
|
|
|
344
382
|
private prefetchAssets;
|
|
345
383
|
private collectAssetMeshIds;
|
|
346
384
|
private collectObjectAssetMeshIds;
|
|
385
|
+
private collectMaterialAssignments;
|
|
386
|
+
private collectObjectMaterialAssignments;
|
|
347
387
|
init(): Promise<void>;
|
|
348
388
|
/**
|
|
349
389
|
* Materialize visual effects to upload textures
|
|
@@ -417,6 +457,12 @@ export declare class SceneMaterializer {
|
|
|
417
457
|
* @returns
|
|
418
458
|
*/
|
|
419
459
|
materialize(source: SceneObject, parent?: Object3D, anonymous?: boolean, context?: MaterializeContext): Promise<Object3D>;
|
|
460
|
+
private handlePaintedScatterSceneMutation;
|
|
461
|
+
private refreshPaintedScatterPresence;
|
|
462
|
+
private disposePaintedScatterManager;
|
|
463
|
+
private sceneContainsPaintedScatter;
|
|
464
|
+
private sceneObjectsContainPaintedScatter;
|
|
465
|
+
private sceneObjectContainsPaintedScatter;
|
|
420
466
|
private handleSurfaceScatterSceneMutation;
|
|
421
467
|
private refreshSurfaceScatterPresence;
|
|
422
468
|
private disposeSurfaceScatterManager;
|
|
@@ -425,6 +471,7 @@ export declare class SceneMaterializer {
|
|
|
425
471
|
private sceneObjectContainsSurfaceScatter;
|
|
426
472
|
private sceneReferencesPrefabAsset;
|
|
427
473
|
private prefabAssetContainsSurfaceScatter;
|
|
474
|
+
private prefabAssetContainsPaintedScatter;
|
|
428
475
|
private pmremGenerator?;
|
|
429
476
|
private pmremGeneratorResults;
|
|
430
477
|
private applyTransform;
|