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.

256 lines • 10.2 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 { Color, LinearSRGBColorSpace, Texture } from 'three'; import SimpleStateBehavior from "three-mesh-ui/examples/behaviors/states/SimpleStateBehavior.js"; import { serializable } from '../../engine/engine_serialization_decorator.js'; import { NEEDLE_progressive } from '../../engine/extensions/NEEDLE_progressive.js'; import { RGBAColor } from "../../engine/js-extensions/index.js"; import { GameObject } from '../Component.js'; import { BaseUIComponent } from "./BaseUIComponent.js"; import { Outline } from './Outline.js'; import { RectTransform } from './RectTransform.js'; import { onChange, scheduleAction } from "./Utils.js"; const _colorStateObject = { backgroundColor: new Color(1, 1, 1), backgroundOpacity: 1, borderColor: new Color(1, 1, 1), borderOpacity: 1, }; /** * @category User Interface * @group Components */ export class Graphic extends BaseUIComponent { get isGraphic() { return true; } get color() { if (!this._color) this._color = new RGBAColor(1, 1, 1, 1); return this._color; } set color(col) { const changed = !this._color || this._color.r !== col.r || this._color.g !== col.g || this._color.b !== col.b || this._color.alpha !== col.alpha; if (!changed) return; if (!this._color) { this._color = new RGBAColor(1, 1, 1, 1); } this._color.copy(col); this.onColorChanged(); } _alphaFactor = 1; setAlphaFactor(factor) { this._alphaFactor = factor; this.onColorChanged(); } get alphaFactor() { return this._alphaFactor; } sRGBColor = new Color(1, 0, 1); onColorChanged() { if (this.uiObject) { this.sRGBColor.copy(this._color); this.sRGBColor.convertLinearToSRGB(); _colorStateObject.backgroundColor = this.sRGBColor; _colorStateObject.backgroundOpacity = this._color.alpha * this._alphaFactor; this.applyEffects(_colorStateObject, this._alphaFactor); this.uiObject.set(_colorStateObject); this.markDirty(); } } // used via animations get m_Color() { return this._color; } raycastTarget = true; uiObject = null; _color = null; _rect = null; _stateManager = null; get rectTransform() { if (!this._rect) { this._rect = GameObject.getComponent(this.gameObject, RectTransform); } if (!this._rect) throw new Error("Not Supported: Make sure to add a RectTransform component before adding a UI Graphic component."); return this._rect; } onParentRectTransformChanged() { this.uiObject?.set({ width: this.rectTransform.width, height: this.rectTransform.height }); this.markDirty(); } __internalNewInstanceCreated(init) { super.__internalNewInstanceCreated(init); this._rect = null; this.uiObject = null; this._stateManager = null; if (this._color) this._color = this._color.clone(); return this; } setState(state) { this.makePanel(); if (this.uiObject) { //@ts-ignore this.uiObject.setState(state); this?.markDirty(); } } setupState(state) { this.makePanel(); if (this.uiObject) { // @marwie : v7.x now have a concurrent state management in core mimicking html/css // ie : (::firstChild::hover::disabled) where firstchild, hover and disabled are all on different channels // In order to keep needle Raycaster and EventSystem intact, I added in v7 a SimpleStateBehavior, which acts as previously if (!this._stateManager) this._stateManager = new SimpleStateBehavior(this.uiObject); //@ts-ignore this.uiObject.setupState(state.state, state.attributes); } } setOptions(opts) { this.makePanel(); if (this.uiObject) { //@ts-ignore this.uiObject.set(opts); // if (opts["backgroundColor"] !== undefined || opts["backgroundOpacity"] !== undefined) // this.uiObject["updateBackgroundMaterial"]?.call(this.uiObject); } } awake() { super.awake(); this.makePanel(); // when _color is written to onChange(this, "_color", () => scheduleAction(this, this.onColorChanged)); } onEnable() { super.onEnable(); if (this.uiObject) { this.rectTransform.shadowComponent?.add(this.uiObject); this.addShadowComponent(this.uiObject, this.rectTransform); } } onDisable() { super.onDisable(); if (this.uiObject) this.removeShadowComponent(); } _currentlyCreatingPanel = false; makePanel() { if (this.uiObject) return; if (this._currentlyCreatingPanel) return; this._currentlyCreatingPanel = true; const offset = .015; // if (this.Root) offset = .02 * (1 / this.Root.gameObject.scale.z); const opts = { backgroundColor: this.color, backgroundOpacity: this.color.alpha, offset: offset, // without a tiny offset we get z fighting }; this.onBeforeCreate(opts); this.applyEffects(opts); this.onCreate(opts); this.controlsChildLayout = false; this._currentlyCreatingPanel = false; this.onAfterCreated(); this.onColorChanged(); } onBeforeCreate(_opts) { } onCreate(opts) { this.uiObject = this.rectTransform.createNewBlock(opts); this.uiObject.name = this.name; } onAfterCreated() { } applyEffects(opts, alpha = 1) { const outline = this.gameObject?.getComponent(Outline); if (outline) { if (outline.effectDistance) opts.borderWidth = Math.max(Math.abs(outline.effectDistance.x), Math.abs(outline.effectDistance.y)); if (outline.effectColor) { opts.borderColor = outline.effectColor; opts.borderOpacity = outline.effectColor.alpha * alpha; } } } /** used internally to ensure textures assigned to UI use linear encoding */ static textureCache = new Map(); async setTexture(tex) { this.setOptions({ backgroundOpacity: 0 }); if (tex) { // workaround for https://github.com/needle-tools/needle-engine-support/issues/109 // if (tex.colorSpace === SRGBColorSpace || !tex.colorSpace || true) { if (Graphic.textureCache.has(tex)) { tex = Graphic.textureCache.get(tex); } else { if (tex.isRenderTargetTexture) { // we can not clone the texture if it's a render target // otherwise it won't be updated anymore in the UI // TODO: below maskable graphic is flipped but settings a rendertexture results in the texture being upside down. // we should remove the flip below (scale.y *= -1) but this needs to be tested with all UI components } else { const clone = tex.clone(); clone.colorSpace = LinearSRGBColorSpace; Graphic.textureCache.set(tex, clone); tex = clone; } } // } this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" }); NEEDLE_progressive.assignTextureLOD(tex, 0).then(res => { if (res instanceof Texture) { if (tex) Graphic.textureCache.set(tex, res); this.setOptions({ backgroundImage: res }); this.markDirty(); } }); } else { this.setOptions({ backgroundImage: undefined, borderRadius: 0, backgroundOpacity: this.color.alpha }); } this.markDirty(); } onAfterAddedToScene() { super.onAfterAddedToScene(); if (this.shadowComponent) { // @TODO: I think we dont even need this anymore and this leads to the offset being applied twice //@ts-ignore this.shadowComponent.offset = this.shadowComponent.position.z; // console.log(this.shadowComponent); // setTimeout(()=>{ // this.shadowComponent?.traverse(c => { // console.log(c); // if(c.material) c.material.depthTest = false; // }); // },1000); } } } __decorate([ serializable(RGBAColor) ], Graphic.prototype, "color", null); __decorate([ serializable() ], Graphic.prototype, "raycastTarget", void 0); /** * @category User Interface * @group Components */ export class MaskableGraphic extends Graphic { _flippedObject = false; onAfterCreated() { // flip image if (this.uiObject && !this._flippedObject) { this._flippedObject = true; this.uiObject.scale.y *= -1; } } } //# sourceMappingURL=Graphic.js.map