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.

835 lines • 36.1 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { getRaycastMesh } from "@needle-tools/gltf-progressive"; import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Vector4 } from "three"; import { showBalloonWarning } from "../engine/debug/index.js"; import { getComponent, getOrAddComponent } from "../engine/engine_components.js"; import { Gizmos } from "../engine/engine_gizmos.js"; import { InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js"; import { isLocalNetwork } from "../engine/engine_networking_utils.js"; import { serializable } from "../engine/engine_serialization_decorator.js"; import { FrameEvent } from "../engine/engine_setup.js"; import { getTempVector } from "../engine/engine_three_utils.js"; import { getParam } from "../engine/engine_utils.js"; import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects.js"; import { setCustomVisibility } from "../engine/js-extensions/Layers.js"; import { Behaviour, GameObject } from "./Component.js"; import { ReflectionProbe } from "./ReflectionProbe.js"; import { InstancingHandler } from "./RendererInstancing.js"; // import { RendererCustomShader } from "./RendererCustomShader.js"; import { RendererLightmap } from "./RendererLightmap.js"; // for staying compatible with old code export { InstancingUtil } from "../engine/engine_instancing.js"; const debugRenderer = getParam("debugrenderer"); const debugskinnedmesh = getParam("debugskinnedmesh"); const suppressInstancing = getParam("noinstancing"); const showWireframe = getParam("wireframe"); export var ReflectionProbeUsage; (function (ReflectionProbeUsage) { ReflectionProbeUsage[ReflectionProbeUsage["Off"] = 0] = "Off"; ReflectionProbeUsage[ReflectionProbeUsage["BlendProbes"] = 1] = "BlendProbes"; ReflectionProbeUsage[ReflectionProbeUsage["BlendProbesAndSkybox"] = 2] = "BlendProbesAndSkybox"; ReflectionProbeUsage[ReflectionProbeUsage["Simple"] = 3] = "Simple"; })(ReflectionProbeUsage || (ReflectionProbeUsage = {})); export class FieldWithDefault { path = null; asset = null; default; } export var RenderState; (function (RenderState) { RenderState[RenderState["Both"] = 0] = "Both"; RenderState[RenderState["Back"] = 1] = "Back"; RenderState[RenderState["Front"] = 2] = "Front"; })(RenderState || (RenderState = {})); // support sharedMaterials[index] assigning materials directly to the objects class SharedMaterialArray { _renderer; _targets = []; _indexMapMaxIndex; _indexMap; _changed = false; get changed() { return this._changed; } set changed(value) { if (value === true) { if (debugRenderer) console.warn("SharedMaterials have changed: " + this._renderer.name, this); } this._changed = value; } is(renderer) { return this._renderer === renderer; } constructor(renderer, originalMaterials) { this._renderer = renderer; const setMaterial = this.setMaterial.bind(this); const getMaterial = this.getMaterial.bind(this); const go = renderer.gameObject; this._targets = []; if (go) { switch (go.type) { case "Group": this._targets = [...go.children]; break; case "SkinnedMesh": case "Mesh": this._targets.push(go); break; } } // this is useful to have an index map when e.g. materials are trying to be assigned by index let hasMissingMaterials = false; let indexMap = undefined; let maxIndex = 0; for (let i = 0; i < this._targets.length; i++) { const target = this._targets[i]; if (!target) continue; const mat = target.material; if (!mat) continue; // set the shadow side to the same as the side of the material, three flips this for some reason mat.shadowSide = mat.side; for (let k = 0; k < originalMaterials.length; k++) { const orig = originalMaterials[k]; if (!orig) { hasMissingMaterials = true; continue; } if (mat.name === orig.name) { if (indexMap === undefined) indexMap = new Map(); indexMap.set(k, i); maxIndex = Math.max(maxIndex, k); // console.log(`Material ${mat.name} at ${k} was found at index ${i} in renderer ${renderer.name}.`) break; } } } if (hasMissingMaterials) { this._indexMapMaxIndex = maxIndex; this._indexMap = indexMap; const warningMessage = `Renderer ${renderer.name} was initialized with missing materials - this may lead to unexpected behaviour when trying to access sharedMaterials by index.`; console.warn(warningMessage); if (isLocalNetwork()) showBalloonWarning("Found renderer with missing materials: please check the console for details."); } // this lets us override the javascript indexer, only works in ES6 tho // but like that we can use sharedMaterials[index] and it will be assigned to the object directly return new Proxy(this, { get(target, key) { if (typeof key === "string") { const index = parseInt(key); if (!isNaN(index)) { return getMaterial(index); } } return target[key]; }, set(target, key, value) { if (typeof key === "string") setMaterial(value, Number.parseInt(key)); // console.log(target, key, value); if (Reflect.set(target, key, value)) { if (value instanceof Material) target.changed = true; return true; } return false; } }); } get length() { if (this._indexMapMaxIndex !== undefined) return this._indexMapMaxIndex + 1; return this._targets.length; } // iterator to support: for(const mat of sharedMaterials) *[Symbol.iterator]() { for (let i = 0; i < this.length; i++) { yield this.getMaterial(i); } } resolveIndex(index) { const map = this._indexMap; // if we have a index map it means that some materials were missing if (map) { if (map.has(index)) return map.get(index); // return -1; } return index; } setMaterial(mat, index) { index = this.resolveIndex(index); if (index < 0 || index >= this._targets.length) return; const target = this._targets[index]; if (!target || target["material"] === undefined) return; target["material"] = mat; this.changed = true; } getMaterial(index) { index = this.resolveIndex(index); if (index < 0) return null; const obj = this._targets; if (index >= obj.length) return null; const target = obj[index]; if (!target) return null; return target["material"]; } } /** * @category Rendering * @group Components */ export class Renderer extends Behaviour { /** Enable or disable instancing for an object. This will create a Renderer component if it does not exist yet. * @returns the Renderer component that was created or already existed on the object */ static setInstanced(obj, enableInstancing) { const renderer = getOrAddComponent(obj, Renderer); renderer.setInstancingEnabled(enableInstancing); return renderer; } /** Check if an object is currently rendered using instancing * @returns true if the object is rendered using instancing */ static isInstanced(obj) { const renderer = getComponent(obj, Renderer); if (renderer) return renderer.isInstancingActive; return InstancingUtil.isUsingInstancing(obj); } /** Set the rendering state only of an object (makes it visible or invisible) without affecting component state or child hierarchy visibility! You can also just enable/disable the Renderer component on that object for the same effect! * * If you want to activate or deactivate a complete object you can use obj.visible as usual (it acts the same as setActive in Unity) */ static setVisible(obj, visible) { setCustomVisibility(obj, visible); } receiveShadows = false; shadowCastingMode = ShadowCastingMode.Off; lightmapIndex = -1; lightmapScaleOffset = new Vector4(1, 1, 0, 0); /** If the renderer should use instancing * If this is a boolean (true) all materials will be instanced or (false) none of them. * If this is an array of booleans the materials will be instanced based on the index of the material. */ enableInstancing = undefined; renderOrder = undefined; allowOcclusionWhenDynamic = true; probeAnchor; reflectionProbeUsage = ReflectionProbeUsage.Off; // custom shader // get materialProperties(): Array<MaterialProperties> | undefined { // return this._materialProperties; // } // set materialProperties(value: Array<MaterialProperties> | undefined) { // this._materialProperties = value; // } // private customShaderHandler: RendererCustomShader | undefined = undefined; // private _materialProperties: Array<MaterialProperties> | undefined = undefined; _lightmaps; /** Get the mesh Object3D for this renderer * Warn: if this is a multimaterial object it will return the first mesh only * @returns a mesh object3D. * */ get sharedMesh() { if (this.gameObject.type === "Mesh") { return this.gameObject; } else if (this.gameObject.type === "SkinnesMesh") { return this.gameObject; } else if (this.gameObject.type === "Group") { return this.gameObject.children[0]; } return undefined; } _sharedMeshes = []; /** Get all the mesh Object3D for this renderer * @returns an array of mesh object3D. */ get sharedMeshes() { if (this.destroyed || !this.gameObject) return this._sharedMeshes; this._sharedMeshes.length = 0; if (this.gameObject.type === "Group") { for (const ch of this.gameObject.children) { if (ch.type === "Mesh" || ch.type === "SkinnedMesh") { this._sharedMeshes.push(ch); } } } else if (this.gameObject.type === "Mesh" || this.gameObject.type === "SkinnedMesh") { this._sharedMeshes.push(this.gameObject); } return this._sharedMeshes; } get sharedMaterial() { return this.sharedMaterials[0]; } set sharedMaterial(mat) { const cur = this.sharedMaterials[0]; if (cur === mat) return; this.sharedMaterials[0] = mat; this.applyLightmapping(); } /**@deprecated please use sharedMaterial */ get material() { return this.sharedMaterials[0]; } /**@deprecated please use sharedMaterial */ set material(mat) { this.sharedMaterial = mat; } _sharedMaterials; _originalMaterials; _probeAnchorLastFrame; // this is just available during deserialization set sharedMaterials(_val) { // TODO: elements in the array might be missing at the moment which leads to problems if an index is serialized if (!this._originalMaterials) { this._originalMaterials = _val; } else if (_val) { let didWarn = false; for (let i = 0; i < this._sharedMaterials.length; i++) { const mat = i < _val.length ? _val[i] : null; if (mat && mat instanceof Material) { this.sharedMaterials[i] = mat; } else { if (!didWarn) { didWarn = true; console.warn("Can not assign null as material: " + this.name, mat); } } } } } //@ts-ignore get sharedMaterials() { if (!this._sharedMaterials || !this._sharedMaterials.is(this)) { if (!this._originalMaterials) this._originalMaterials = []; this._sharedMaterials = new SharedMaterialArray(this, this._originalMaterials); } return this._sharedMaterials; } static get shouldSuppressInstancing() { return suppressInstancing; } _lightmapTextureOverride = undefined; get lightmap() { if (this._lightmaps?.length) { return this._lightmaps[0].lightmap; } return null; } /** set undefined to return to default lightmap */ set lightmap(tex) { this._lightmapTextureOverride = tex; if (tex === undefined) { tex = this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex); } if (this._lightmaps?.length) { for (const lm of this._lightmaps) { lm.lightmap = tex; } } } get hasLightmap() { const lm = this.lightmap; return lm !== null && lm !== undefined; } allowProgressiveLoading = true; _firstFrame = -1; registering() { if (!this.enabled) { this.setVisibility(false); } } awake() { this._firstFrame = this.context.time.frame; if (debugRenderer) console.log("Renderer ", this.name, this); this.clearInstancingState(); if (this.probeAnchor && debugRenderer) this.probeAnchor.add(new AxesHelper(.2)); this._reflectionProbe = null; if (this.isMultiMaterialObject(this.gameObject)) { for (const child of this.gameObject.children) { this.context.addBeforeRenderListener(child, this.onBeforeRenderThree); child.layers.mask = this.gameObject.layers.mask; } if (this.renderOrder !== undefined) { // Objects can have nested renderers (e.g. contain 2 meshes and then again another group) // or perhaps just regular child objects that have their own renderer component (?) let index = 0; for (let i = 0; i < this.gameObject.children.length; i++) { const ch = this.gameObject.children[i]; // ignore nested groups or objects that have their own renderer (aka their own render order settings) if (!this.isMeshOrSkinnedMesh(ch) || GameObject.getComponent(ch, Renderer)) continue; if (this.renderOrder.length <= index) { console.warn("Incorrect renderOrder element count", this, this.renderOrder.length + " but expected " + this.gameObject.children.length, "Index: " + index, "ChildElement:", ch); continue; } // if(debugRenderer) console.log("Setting render order", ch, this.renderOrder[index]) ch.renderOrder = this.renderOrder[index]; index += 1; } } } // TODO: custom shader with sub materials else if (this.isMeshOrSkinnedMesh(this.gameObject)) { this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree); if (this.renderOrder !== undefined && this.renderOrder.length > 0) this.gameObject.renderOrder = this.renderOrder[0]; } else { this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree); } this.applyLightmapping(); if (showWireframe) { for (let i = 0; i < this.sharedMaterials.length; i++) { const mat = this.sharedMaterials[i]; if (mat) { mat.wireframe = true; } } } } applyLightmapping() { if (this.lightmapIndex >= 0) { const type = this.gameObject.type; // use the override lightmap if its not undefined const tex = this._lightmapTextureOverride !== undefined ? this._lightmapTextureOverride : this.context.lightmaps.tryGetLightmap(this.sourceId, this.lightmapIndex); if (tex) { if (!this._lightmaps) this._lightmaps = []; if (type === "Mesh") { const mat = this.gameObject["material"]; if (!mat?.isMeshBasicMaterial) { if (this._lightmaps.length <= 0) { const rm = new RendererLightmap(this.gameObject, this.context); this._lightmaps.push(rm); } const rm = this._lightmaps[0]; rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex); } else { if (mat) console.warn("Lightmapping is not supported on MeshBasicMaterial", mat.name); } } // for multi materials we need to loop through children // and then we add a lightmap renderer component to each of them else if (this.isMultiMaterialObject(this.gameObject) && this.sharedMaterials.length > 0) { for (let i = 0; i < this.gameObject.children.length; i++) { const child = this.gameObject.children[i]; if (!child["material"]?.isMeshBasicMaterial) { let rm = undefined; if (i >= this._lightmaps.length) { rm = new RendererLightmap(child, this.context); this._lightmaps.push(rm); } else rm = this._lightmaps[i]; rm.init(this.lightmapIndex, this.lightmapScaleOffset, tex); } } } } else { if (debugRenderer) console.warn("Lightmap not found", this.sourceId, this.lightmapIndex); } } } _isInstancingEnabled = false; _handles = undefined; /** * @returns true if this renderer has instanced objects */ get isInstancingActive() { return this._handles != undefined && this._handles.length > 0 && this._isInstancingEnabled; } /** @returns the instancing handles */ get instances() { if (!this._handles || this._handles.length <= 0) { return null; } this._handlesTempArray.length = 0; if (this._handles) { for (const h of this._handles) { this._handlesTempArray.push(h); } } return this._handlesTempArray; } _handlesTempArray = []; /** Enable or disable instancing for this renderer. * @param enabled true to enable instancing, false to disable it */ setInstancingEnabled(enabled) { if (this._isInstancingEnabled === enabled) return enabled && (this._handles === undefined || this._handles != null && this._handles.length > 0); this._isInstancingEnabled = enabled; if (enabled) { if (this.enableInstancing === undefined) this.enableInstancing = true; if (this._handles === undefined) { this._handles = InstancingHandler.instance.setup(this, this.gameObject, this.context, null, { rend: this, foundMeshes: 0, useMatrixWorldAutoUpdate: this.useInstanceMatrixWorldAutoUpdate() }); if (this._handles) { GameObject.markAsInstancedRendered(this.gameObject, true); return true; } } else if (this._handles !== null) { for (const handler of this._handles) { handler.updateInstanceMatrix(true); handler.add(); } GameObject.markAsInstancedRendered(this.gameObject, true); return true; } } else { if (this._handles) { for (const handler of this._handles) { handler.remove(this.destroyed); } } return true; } return false; } clearInstancingState() { this._isInstancingEnabled = false; this._handles = undefined; } /** Return true to wrap matrix update events for instanced rendering to update instance matrices automatically when matrixWorld changes * This is a separate method to be overrideable from user code */ useInstanceMatrixWorldAutoUpdate() { return true; } start() { if (this.enableInstancing && !suppressInstancing) { this.setInstancingEnabled(true); // make sure the instance is marked dirty once for cases where e.g. an animator animates the instanced object // in the first frame we want the updated matrix then to be applied immediately to the instancing InstancingUtil.markDirty(this.gameObject); } this.gameObject.frustumCulled = this.allowOcclusionWhenDynamic; if (this.isMultiMaterialObject(this.gameObject)) { for (let i = 0; i < this.gameObject.children.length; i++) { const ch = this.gameObject.children[i]; ch.frustumCulled = this.allowOcclusionWhenDynamic; } } } onEnable() { // ensure shared meshes are initialized const _ = this.sharedMeshes; this.setVisibility(true); // Check if the renderer is using instancing (or any child object is supposed to use instancing) const isUsingInstancing = this._isInstancingEnabled || (this.enableInstancing == true || (Array.isArray(this.enableInstancing) && this.enableInstancing.some(x => x))); if (isUsingInstancing) { if (this.__internalDidAwakeAndStart) this.setInstancingEnabled(true); } // if no insancing is used we can apply the stencil settings // but instancing and stencil at the same time is not supported else if (this.enabled) { this.applyStencil(); } this.updateReflectionProbe(); // this.testIfLODLevelsAreAvailable(); } onDisable() { this.setVisibility(false); if (this._handles && this._handles.length > 0) { this.setInstancingEnabled(false); } } onDestroy() { this._handles = null; if (this.isMultiMaterialObject(this.gameObject)) { for (const child of this.gameObject.children) { this.context.removeBeforeRenderListener(child, this.onBeforeRenderThree); } } else { this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree); } } onBeforeRender() { if (!this.gameObject) { return; } if (this._probeAnchorLastFrame !== this.probeAnchor) { this._reflectionProbe?.onUnset(this); this.updateReflectionProbe(); } if (debugRenderer == this.name && this.gameObject instanceof Mesh) { this.gameObject.geometry.computeBoundingSphere(); const tempCenter = getTempVector(this.gameObject.geometry.boundingSphere.center).applyMatrix4(this.gameObject.matrixWorld); Gizmos.DrawWireSphere(tempCenter, this.gameObject.geometry.boundingSphere.radius, 0x00ddff); } if (this.isMultiMaterialObject(this.gameObject) && this.gameObject.children?.length > 0) { for (const ch of this.gameObject.children) { this.applySettings(ch); } } else { this.applySettings(this.gameObject); } if (this.sharedMaterials.changed) { this.sharedMaterials.changed = false; this.applyLightmapping(); } if (this._handles?.length) { // if (this.name === "Darbouka") // console.log(this.name, this.gameObject.matrixWorldNeedsUpdate); const needsUpdate = this.gameObject[NEED_UPDATE_INSTANCE_KEY] === true; // || this.gameObject.matrixWorldNeedsUpdate; if (needsUpdate) { // if (debugInstancing) console.log("UPDATE INSTANCED MATRICES at frame #" + this.context.time.frame); this.gameObject[NEED_UPDATE_INSTANCE_KEY] = false; const remove = false; // Math.random() < .01; for (let i = this._handles.length - 1; i >= 0; i--) { const h = this._handles[i]; if (remove) { h.remove(this.destroyed); this._handles.splice(i, 1); } else h.updateInstanceMatrix(); } this.gameObject.matrixWorldNeedsUpdate = false; } } if (this._handles && this._handles.length <= 0) { GameObject.markAsInstancedRendered(this.gameObject, false); } if (this._isInstancingEnabled && this._handles) { for (let i = 0; i < this._handles.length; i++) { const handle = this._handles[i]; setCustomVisibility(handle.object, false); } } if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) { this._reflectionProbe.onSet(this); } // since three 163 we need to set the envMap to the scene envMap if it is not set // otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903 // internal issue: https://linear.app/needle/issue/NE-6363 for (const mat of this._sharedMaterials) { // If the material has a envMap and is NOT using a reflection probe we set the envMap to the scene environment if (mat && "envMap" in mat && "envMapIntensity" in mat && !ReflectionProbe.isUsingReflectionProbe(mat)) { mat.envMap = this.context.scene.environment; } } } onBeforeRenderThree = (_renderer, _scene, _camera, _geometry, material, _group) => { if (material.envMapIntensity !== undefined) { const factor = this.hasLightmap ? Math.PI : 1; const environmentIntensity = this.context.mainCameraComponent?.environmentIntensity ?? 1; material.envMapIntensity = Math.max(0, environmentIntensity * this.context.sceneLighting.environmentIntensity / factor); } if (this._lightmaps) { for (const lm of this._lightmaps) { lm.updateLightmapUniforms(material); lm.applyLightmap(); } } }; onAfterRender() { if (this._isInstancingEnabled && this._handles) { for (let i = 0; i < this._handles.length; i++) { const handle = this._handles[i]; setCustomVisibility(handle.object, true); } } if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) { this._reflectionProbe.onUnset(this); } if (this.static && this.gameObject.matrixAutoUpdate) { this.gameObject.matrixAutoUpdate = false; } } /** Applies stencil settings for this renderer's objects (if stencil settings are available) */ applyStencil() { NEEDLE_render_objects.applyStencil(this); } /** Apply the settings of this renderer to the given object * Settings include shadow casting and receiving (e.g. this.receiveShadows, this.shadowCastingMode) */ applySettings(go) { go.receiveShadow = this.receiveShadows; if (this.shadowCastingMode == ShadowCastingMode.On) { go.castShadow = true; } else go.castShadow = false; } _reflectionProbe = null; updateReflectionProbe() { // handle reflection probe this._reflectionProbe = null; if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off) { // update the reflection probe right before rendering // if we do it immediately the reflection probe might not be enabled yet // (since this method is called from onEnable) this.startCoroutine(this._updateReflectionProbe(), FrameEvent.LateUpdate); this._probeAnchorLastFrame = this.probeAnchor; } } *_updateReflectionProbe() { const obj = this.probeAnchor || this.gameObject; const isAnchor = this.probeAnchor ? true : false; this._reflectionProbe = ReflectionProbe.get(obj, this.context, isAnchor, this.probeAnchor); } setVisibility(visible) { if (!this.isMultiMaterialObject(this.gameObject)) { setCustomVisibility(this.gameObject, visible); } else { for (const ch of this.gameObject.children) { if (this.isMeshOrSkinnedMesh(ch)) { setCustomVisibility(ch, visible); } } } } isMultiMaterialObject(obj) { return obj.type === "Group"; } isMeshOrSkinnedMesh(obj) { return obj.type === "Mesh" || obj.type === "SkinnedMesh"; } } __decorate([ serializable() ], Renderer.prototype, "receiveShadows", void 0); __decorate([ serializable() ], Renderer.prototype, "shadowCastingMode", void 0); __decorate([ serializable() ], Renderer.prototype, "lightmapIndex", void 0); __decorate([ serializable(Vector4) ], Renderer.prototype, "lightmapScaleOffset", void 0); __decorate([ serializable() ], Renderer.prototype, "enableInstancing", void 0); __decorate([ serializable() ], Renderer.prototype, "renderOrder", void 0); __decorate([ serializable() ], Renderer.prototype, "allowOcclusionWhenDynamic", void 0); __decorate([ serializable(Object3D) ], Renderer.prototype, "probeAnchor", void 0); __decorate([ serializable() ], Renderer.prototype, "reflectionProbeUsage", void 0); export class MeshRenderer extends Renderer { } export class SkinnedMeshRenderer extends MeshRenderer { _needUpdateBoundingSphere = false; // private _lastWorldPosition = new Vector3(); awake() { super.awake(); if (debugskinnedmesh) console.log("SkinnedMeshRenderer for \"" + this.name + "\"", this); // disable skinned mesh occlusion because of https://github.com/mrdoob/js/issues/14499 this.allowOcclusionWhenDynamic = false; for (const mesh of this.sharedMeshes) { // If we don't do that here the bounding sphere matrix used for raycasts will be wrong. Not sure *why* this is necessary mesh.parent?.updateWorldMatrix(false, true); this.markBoundsDirty(); } } onAfterRender() { super.onAfterRender(); // this.gameObject.parent.position.x += Math.sin(this.context.time.time) * .01; // if (this.gameObject instanceof SkinnedMesh && this.gameObject.geometry.boundingSphere) { // const bounds = this.gameObject.geometry.boundingSphere; // const worldpos = getTempVector().setFromMatrixPosition(this.gameObject.matrixWorld); // if (worldpos.distanceTo(this._lastWorldPosition) > bounds.radius) { // this._lastWorldPosition.copy(worldpos); // this.markBoundsDirty(); // }; // } if (this._needUpdateBoundingSphere) { for (const mesh of this.sharedMeshes) { if (mesh instanceof SkinnedMesh) { this._needUpdateBoundingSphere = false; try { const geometry = mesh.geometry; const raycastmesh = getRaycastMesh(mesh); if (raycastmesh) { mesh.geometry = raycastmesh; } mesh.computeBoundingSphere(); mesh.geometry = geometry; } catch (err) { console.error(`Error updating bounding sphere for ${mesh.name}`, err); } } } } // if (this.context.time.frame % 30 === 0) this.markBoundsDirty(); if (debugskinnedmesh) { for (const mesh of this.sharedMeshes) { if (mesh instanceof SkinnedMesh && mesh.boundingSphere) { const tempCenter = getTempVector(mesh.boundingSphere.center).applyMatrix4(mesh.matrixWorld); Gizmos.DrawWireSphere(tempCenter, mesh.boundingSphere.radius, "red"); } } } } markBoundsDirty() { this._needUpdateBoundingSphere = true; } } export var ShadowCastingMode; (function (ShadowCastingMode) { /// <summary> /// <para>No shadows are cast from this object.</para> /// </summary> ShadowCastingMode[ShadowCastingMode["Off"] = 0] = "Off"; /// <summary> /// <para>Shadows are cast from this object.</para> /// </summary> ShadowCastingMode[ShadowCastingMode["On"] = 1] = "On"; /// <summary> /// <para>Shadows are cast from this object, treating it as two-sided.</para> /// </summary> ShadowCastingMode[ShadowCastingMode["TwoSided"] = 2] = "TwoSided"; /// <summary> /// <para>Object casts shadows, but is otherwise invisible in the Scene.</para> /// </summary> ShadowCastingMode[ShadowCastingMode["ShadowsOnly"] = 3] = "ShadowsOnly"; })(ShadowCastingMode || (ShadowCastingMode = {})); //# sourceMappingURL=Renderer.js.map