@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.
205 lines • 9.12 kB
JavaScript
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 { EquirectangularReflectionMapping, LinearSRGBColorSpace, MeshBasicMaterial, Texture, Vector3 } from "three";
import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
import { serializable } from "../engine/engine_serialization.js";
import { getParam } from "../engine/engine_utils.js";
import { BoxHelperComponent } from "./BoxHelperComponent.js";
import { Behaviour } from "./Component.js";
export const debug = getParam("debugreflectionprobe");
const disable = getParam("noreflectionprobe");
const $reflectionProbeKey = Symbol("reflectionProbeKey");
const $originalMaterial = Symbol("original material");
/**
* @category Rendering
* @group Components
*/
export class ReflectionProbe extends Behaviour {
static _probes = new Map();
static isUsingReflectionProbe(material) {
return !!(material[$reflectionProbeKey] || material[$originalMaterial]?.[$reflectionProbeKey]);
}
static get(object, context, isAnchor, anchor) {
if (!object || object.isObject3D !== true)
return null;
if (disable)
return null;
const probes = ReflectionProbe._probes.get(context);
if (probes) {
for (const probe of probes) {
if (!probe.__didAwake)
probe.__internalAwake();
if (probe.activeAndEnabled) {
if (anchor) {
// test if anchor is reflection probe object
if (probe.gameObject === anchor) {
return probe;
}
}
// TODO not supported right now, as we'd have to pass the ReflectionProbe scale through as well.
else if (probe.isInBox(object)) {
if (debug)
console.log("Found reflection probe", object.name, probe.name);
return probe;
}
}
}
}
if (debug)
console.debug("Did not find reflection probe", object.name, isAnchor, object);
return null;
}
_texture;
// @serializable(Texture)
set texture(tex) {
if (tex && !(tex instanceof Texture)) {
console.error("ReflectionProbe.texture must be a Texture", tex);
return;
}
this._texture = tex;
if (tex) {
tex.mapping = EquirectangularReflectionMapping;
tex.colorSpace = LinearSRGBColorSpace;
tex.needsUpdate = true;
}
}
get texture() {
return this._texture;
}
center;
size;
_boxHelper;
isInBox(obj) {
return this._boxHelper?.isInBox(obj);
}
constructor() {
super();
if (!ReflectionProbe._probes.has(this.context)) {
ReflectionProbe._probes.set(this.context, []);
}
ReflectionProbe._probes.get(this.context)?.push(this);
}
awake() {
this._boxHelper = this.gameObject.addComponent(BoxHelperComponent);
this._boxHelper.updateBox(true);
if (debug)
this._boxHelper.showHelper(0x555500, true);
if (this._texture) {
this._texture.mapping = EquirectangularReflectionMapping;
this._texture.colorSpace = LinearSRGBColorSpace;
this._texture.needsUpdate = true;
}
}
start() {
if (!this._texture && isDevEnvironment()) {
console.warn(`[ReflectionProbe] Missing texture. Please assign a custom cubemap texture. To use reflection probes assign them to your renderer's "anchor" property.`);
showBalloonWarning("ReflectionProbe configuration hint: See browser console for details");
}
}
onDestroy() {
const probes = ReflectionProbe._probes.get(this.context);
if (probes) {
const index = probes.indexOf(this);
if (index >= 0) {
probes.splice(index, 1);
}
}
}
// when objects are rendered and they share material
// and some need reflection probe and some don't
// we need to make sure we don't override the material but use a copy
static _rendererMaterialsCache = new Map();
onSet(_rend) {
if (disable)
return;
if (!this.enabled)
return;
if (_rend.sharedMaterials?.length <= 0)
return;
if (!this.texture)
return;
let rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
if (!rendererCache) {
rendererCache = [];
ReflectionProbe._rendererMaterialsCache.set(_rend, rendererCache);
}
// TODO: dont clone material for every renderer that uses reflection probes, we can do it once per material when they use the same reflection texture
// need to make sure materials are not shared when using reflection probes
// otherwise some renderers outside of the probe will be affected or vice versa
for (let i = 0; i < _rend.sharedMaterials.length; i++) {
const material = _rend.sharedMaterials[i];
if (!material) {
continue;
}
if (material["envMap"] === undefined) {
continue;
}
if (material instanceof MeshBasicMaterial) {
continue;
}
let cached = rendererCache[i];
// make sure we have the currently assigned material cached (and an up to date clone of that)
// TODO: this is causing problems with progressive textures sometimes (depending on the order) when the version changes and we re-create materials over and over. We might want to just set the material envmap instead of making a clone
const isCachedInstance = material === cached?.copy;
const hasChanged = !cached || cached.material.uuid !== material.uuid || cached.copy.version !== material.version;
if (!isCachedInstance && hasChanged) {
if (debug) {
let reason = "";
if (!cached)
reason = "not cached";
else if (cached.material !== material)
reason = "reference changed; cached instance?: " + isCachedInstance;
else if (cached.copy.version !== material.version)
reason = "version changed";
console.warn("Cloning material", material.name, material.version, "Reason:", reason, "\n", material.uuid, "\n", cached?.copy.uuid, "\n", _rend.name);
}
const clone = material.clone();
clone.version = material.version;
if (cached) {
cached.copy = clone;
cached.material = material;
}
else {
cached = {
material: material,
copy: clone
};
rendererCache.push(cached);
}
clone[$reflectionProbeKey] = this;
clone[$originalMaterial] = material;
if (debug)
console.log("Set reflection", _rend.name, _rend.guid);
}
// See NE-4771 and NE-4856
if (cached && cached.copy) {
cached.copy.onBeforeCompile = material.onBeforeCompile;
}
/** this is the material that we copied and that has the reflection probe */
const copy = cached?.copy;
// make sure the reflection probe is assigned
copy["envMap"] = this.texture;
_rend.sharedMaterials[i] = copy;
}
}
onUnset(_rend) {
const rendererCache = ReflectionProbe._rendererMaterialsCache.get(_rend);
if (rendererCache) {
for (let i = 0; i < rendererCache.length; i++) {
const cached = rendererCache[i];
_rend.sharedMaterials[i] = cached.material;
}
}
}
}
__decorate([
serializable(Vector3)
], ReflectionProbe.prototype, "center", void 0);
__decorate([
serializable(Vector3)
], ReflectionProbe.prototype, "size", void 0);
//# sourceMappingURL=ReflectionProbe.js.map