@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.
203 lines • 9.15 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 { Object3D, Quaternion, Vector3 } from "three";
import { WaitForSeconds } from "../engine/engine_coroutine.js";
import { InstantiateOptions } from "../engine/engine_gameobject.js";
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { Behaviour, GameObject } from "./Component.js";
import { DragControls, DragMode } from "./DragControls.js";
import { SyncedTransform } from "./SyncedTransform.js";
import { ObjectRaycaster } from "./ui/Raycaster.js";
/**
* The Duplicatable component is used to duplicate a assigned {@link GameObject} when a pointer event occurs on the GameObject.
* It implements the {@link IPointerEventHandler} interface and can be used to expose duplication to the user in the editor without writing code.
* @category Interactivity
* @group Components
*/
export class Duplicatable extends Behaviour {
/** Duplicates will be parented into the set object. If not defined, this GameObject will be used as parent. */
parent = null;
/** The object to be duplicated. If no object is assigned then the object the Duplicatable component is attached to will be used for cloning.
* @default null
*/
object = null;
/**
* The maximum number of objects that can be duplicated in the interval.
* @default 60
*/
limitCount = 60;
_currentCount = 0;
_startPosition = null;
_startQuaternion = null;
start() {
this._currentCount = 0;
this._startPosition = null;
this._startQuaternion = null;
if (!this.object) {
this.object = this.gameObject;
}
if (this.object) {
if (this.object === this.gameObject) {
// console.error("Can not duplicate self");
// return;
const instanceIdProvider = new InstantiateIdProvider(this.guid);
this.object = GameObject.instantiate(this.object, { idProvider: instanceIdProvider, keepWorldPosition: false, });
const duplicatable = GameObject.getComponent(this.object, Duplicatable);
duplicatable?.destroy();
let dragControls = this.object.getComponentInChildren(DragControls);
if (!dragControls) {
dragControls = this.object.addComponent(DragControls, {
dragMode: DragMode.SnapToSurfaces
});
dragControls.guid = instanceIdProvider.generateUUID();
}
let syncedTransfrom = GameObject.getComponent(dragControls.gameObject, SyncedTransform);
if (!syncedTransfrom) {
syncedTransfrom = dragControls.gameObject.addComponent(SyncedTransform);
syncedTransfrom.guid = instanceIdProvider.generateUUID();
}
}
this.object.visible = false;
// legacy – DragControls was required for duplication and so often the component is still there; we work around that by disabling it here
const dragControls = this.gameObject.getComponent(DragControls);
if (dragControls) {
// if (isDevEnvironment()) console.warn(`Please remove DragControls from \"${dragControls.name}\": it's not needed anymore when the object also has a Duplicatable component`);
dragControls.enabled = false;
}
// when this is in a moveable parent in multiuser scenario somehow the object position gets an offset and might stay that way
// this is just a workaround to set the object position before duplicating
this._startPosition = this.object.position?.clone() ?? new Vector3(0, 0, 0);
this._startQuaternion = this.object.quaternion?.clone() ?? new Quaternion(0, 0, 0, 1);
}
if (!this.gameObject.getComponentInParent(ObjectRaycaster))
this.gameObject.addComponent(ObjectRaycaster);
}
onEnable() {
this.startCoroutine(this.cloneLimitIntervalFn());
}
_forwardPointerEvents = new Map();
onPointerEnter(args) {
if (args.used)
return;
if (!this.object)
return;
if (!this.context.connection.allowEditing)
return;
if (args.button !== 0)
return;
this.context.input.setCursor("pointer");
}
onPointerExit(args) {
if (args.used)
return;
if (!this.object)
return;
if (!this.context.connection.allowEditing)
return;
if (args.button !== 0)
return;
this.context.input.unsetCursor("pointer");
}
/** @internal */
onPointerDown(args) {
if (args.used)
return;
if (!this.object)
return;
if (!this.context.connection.allowEditing)
return;
if (args.button !== 0)
return;
const res = this.handleDuplication();
if (res) {
const dragControls = GameObject.getComponent(res, DragControls);
if (!dragControls)
console.warn("Duplicated object does not have DragControls", res);
else {
dragControls.onPointerDown(args);
this._forwardPointerEvents.set(args.event.space, dragControls);
}
}
else {
if (this._currentCount >= this.limitCount) {
console.warn(`[Duplicatable] Limit of ${this.limitCount} objects created within a few seconds reached. Please wait a moment before creating more objects.`);
}
else {
console.warn(`[Duplicatable] Could not duplicate object.`);
}
}
}
/** @internal */
onPointerUp(args) {
if (args.used)
return;
const dragControls = this._forwardPointerEvents.get(args.event.space);
if (dragControls) {
dragControls.onPointerUp(args);
this._forwardPointerEvents.delete(args.event.space);
}
}
*cloneLimitIntervalFn() {
while (this.activeAndEnabled && !this.destroyed) {
if (this._currentCount > 0) {
this._currentCount -= 1;
}
else if (this._currentCount < 0) {
this._currentCount = 0;
}
yield WaitForSeconds(1);
}
}
handleDuplication() {
if (!this.object)
return null;
if (this.limitCount > 0 && this._currentCount >= this.limitCount)
return null;
if (this.object === this.gameObject)
return null;
if (GameObject.isDestroyed(this.object)) {
this.object = null;
return null;
}
this.object.visible = true;
if (this._startPosition)
this.object.position.copy(this._startPosition);
if (this._startQuaternion)
this.object.quaternion.copy(this._startQuaternion);
const opts = new InstantiateOptions();
if (!this.parent)
this.parent = this.gameObject.parent;
if (this.parent) {
opts.parent = this.parent.guid ?? this.parent.userData?.guid;
opts.keepWorldPosition = true;
}
opts.position = this.worldPosition;
opts.rotation = this.worldQuaternion;
opts.context = this.context;
this._currentCount += 1;
const newInstance = GameObject.instantiateSynced(this.object, opts);
console.assert(newInstance !== this.object, "Duplicated object is original");
this.object.visible = false;
// see if this fixes object being offset when duplicated and dragged - it looks like three clone has shared position/quaternion objects?
if (this._startPosition)
this.object.position.clone().copy(this._startPosition);
if (this._startQuaternion)
this.object.quaternion.clone().copy(this._startQuaternion);
return newInstance;
}
}
__decorate([
serializable(Object3D)
], Duplicatable.prototype, "parent", void 0);
__decorate([
serializable(Object3D)
], Duplicatable.prototype, "object", void 0);
__decorate([
serializable()
], Duplicatable.prototype, "limitCount", void 0);
//# sourceMappingURL=Duplicatable.js.map