@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
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 { 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