UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

370 lines (369 loc) 12.5 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { Color } from "../../core/math/color.js"; import { Curve } from "../../core/math/curve.js"; import { CurveSet } from "../../core/math/curve-set.js"; import { Vec2 } from "../../core/math/vec2.js"; import { Vec3 } from "../../core/math/vec3.js"; import { Vec4 } from "../../core/math/vec4.js"; import { GraphNode } from "../../scene/graph-node.js"; import { Asset } from "../asset/asset.js"; const components = ["x", "y", "z", "w"]; const vecLookup = [void 0, void 0, Vec2, Vec3, Vec4]; function rawToValue(app, args, value, old) { switch (args.type) { case "boolean": return !!value; case "number": if (typeof value === "number") { return value; } else if (typeof value === "string") { const v = parseInt(value, 10); if (isNaN(v)) return null; return v; } else if (typeof value === "boolean") { return 0 + value; } return null; case "json": { const result = {}; if (Array.isArray(args.schema)) { if (!value || typeof value !== "object") { value = {}; } for (let i = 0; i < args.schema.length; i++) { const field = args.schema[i]; if (!field.name) continue; if (field.array) { result[field.name] = []; const arr = Array.isArray(value[field.name]) ? value[field.name] : []; for (let j = 0; j < arr.length; j++) { result[field.name].push(rawToValue(app, field, arr[j])); } } else { const val = value.hasOwnProperty(field.name) ? value[field.name] : field.default; result[field.name] = rawToValue(app, field, val); } } } return result; } case "asset": if (value instanceof Asset) { return value; } else if (typeof value === "number") { return app.assets.get(value) || null; } else if (typeof value === "string") { return app.assets.get(parseInt(value, 10)) || null; } return null; case "entity": if (value instanceof GraphNode) { return value; } else if (typeof value === "string") { return app.getEntityFromIndex(value); } return null; case "rgb": case "rgba": if (value instanceof Color) { if (old instanceof Color) { old.copy(value); return old; } return value.clone(); } else if (value instanceof Array && value.length >= 3 && value.length <= 4) { for (let i = 0; i < value.length; i++) { if (typeof value[i] !== "number") { return null; } } if (!old) old = new Color(); old.r = value[0]; old.g = value[1]; old.b = value[2]; old.a = value.length === 3 ? 1 : value[3]; return old; } else if (typeof value === "string" && /#(?:[0-9a-f]{2}){3,4}/i.test(value)) { if (!old) { old = new Color(); } old.fromString(value); return old; } return null; case "vec2": case "vec3": case "vec4": { const len = parseInt(args.type.slice(3), 10); const vecType = vecLookup[len]; if (value instanceof vecType) { if (old instanceof vecType) { old.copy(value); return old; } return value.clone(); } else if (value instanceof Array && value.length === len) { for (let i = 0; i < value.length; i++) { if (typeof value[i] !== "number") { return null; } } if (!old) old = new vecType(); for (let i = 0; i < len; i++) { old[components[i]] = value[i]; } return old; } return null; } case "curve": if (value) { let curve; if (value instanceof Curve || value instanceof CurveSet) { curve = value.clone(); } else { const CurveType = value.keys[0] instanceof Array ? CurveSet : Curve; curve = new CurveType(value.keys); curve.type = value.type; } return curve; } break; } return value; } function attributeToValue(app, schema, value, current) { if (schema.array) { return value.map((item, index) => rawToValue(app, schema, item, current ? current[index] : null)); } return rawToValue(app, schema, value, current); } function assignAttributesToScript(app, attributeSchemaMap, data, script) { if (!data) return; for (const attributeName in attributeSchemaMap) { const attributeSchema = attributeSchemaMap[attributeName]; const dataToAssign = data[attributeName]; if (dataToAssign === void 0) continue; script[attributeName] = attributeToValue(app, attributeSchema, dataToAssign, script[attributeName]); } } const _ScriptAttributes = class _ScriptAttributes { /** * Create a new ScriptAttributes instance. * * @param {typeof ScriptType} scriptType - Script Type that attributes relate to. */ constructor(scriptType) { this.scriptType = scriptType; this.index = {}; } /** * Add Attribute. * * @param {string} name - Name of an attribute. * @param {object} args - Object with Arguments for an attribute. * @param {("boolean"|"number"|"string"|"json"|"asset"|"entity"|"rgb"|"rgba"|"vec2"|"vec3"|"vec4"|"curve")} args.type - Type * of an attribute value. Can be: * * - "asset" * - "boolean" * - "curve" * - "entity" * - "json" * - "number" * - "rgb" * - "rgba" * - "string" * - "vec2" * - "vec3" * - "vec4" * * @param {*} [args.default] - Default attribute value. * @param {string} [args.title] - Title for Editor's for field UI. * @param {string} [args.description] - Description for Editor's for field UI. * @param {string|string[]} [args.placeholder] - Placeholder for Editor's for field UI. * For multi-field types, such as vec2, vec3, and others use array of strings. * @param {boolean} [args.array] - If attribute can hold single or multiple values. * @param {number} [args.size] - If attribute is array, maximum number of values can be set. * @param {number} [args.min] - Minimum value for type 'number', if max and min defined, slider * will be rendered in Editor's UI. * @param {number} [args.max] - Maximum value for type 'number', if max and min defined, slider * will be rendered in Editor's UI. * @param {number} [args.precision] - Level of precision for field type 'number' with floating * values. * @param {number} [args.step] - Step value for type 'number'. The amount used to increment the * value when using the arrow keys in the Editor's UI. * @param {string} [args.assetType] - Name of asset type to be used in 'asset' type attribute * picker in Editor's UI, defaults to '*' (all). * @param {string[]} [args.curves] - List of names for Curves for field type 'curve'. * @param {string} [args.color] - String of color channels for Curves for field type 'curve', * can be any combination of `rgba` characters. Defining this property will render Gradient in * Editor's field UI. * @param {object[]} [args.enum] - List of fixed choices for field, defined as array of objects, * where key in object is a title of an option. * @param {object[]} [args.schema] - List of attributes for type 'json'. Each attribute * description is an object with the same properties as regular script attributes but with an * added 'name' field to specify the name of each attribute in the JSON. * @example * PlayerController.attributes.add('fullName', { * type: 'string' * }); * @example * PlayerController.attributes.add('speed', { * type: 'number', * title: 'Speed', * placeholder: 'km/h', * default: 22.2 * }); * @example * PlayerController.attributes.add('resolution', { * type: 'number', * default: 32, * enum: [ * { '32x32': 32 }, * { '64x64': 64 }, * { '128x128': 128 } * ] * }); * @example * PlayerController.attributes.add('config', { * type: 'json', * schema: [{ * name: 'speed', * type: 'number', * title: 'Speed', * placeholder: 'km/h', * default: 22.2 * }, { * name: 'resolution', * type: 'number', * default: 32, * enum: [ * { '32x32': 32 }, * { '64x64': 64 }, * { '128x128': 128 } * ] * }] * }); */ add(name, args) { if (!args) { Debug.error(`Cannot add attribute '${name}' to script type '${this.scriptType.name}': args parameter is required`); return; } if (!args.type) { Debug.error(`Cannot add attribute '${name}' to script type '${this.scriptType.name}': args.type is required`); return; } if (this.index[name]) { Debug.warn(`attribute '${name}' is already defined for script type '${this.scriptType.name}'`); return; } else if (_ScriptAttributes.reservedNames.has(name)) { Debug.warn(`attribute '${name}' is a reserved attribute name`); return; } this.index[name] = args; Object.defineProperty(this.scriptType.prototype, name, { get: function() { return this.__attributes[name]; }, set: function(raw) { const evt = "attr"; const evtName = `attr:${name}`; const old = this.__attributes[name]; let oldCopy = old; if (old && args.type !== "json" && args.type !== "entity" && old.clone) { if (this.hasEvent(evt) || this.hasEvent(evtName)) { oldCopy = old.clone(); } } if (args.array) { this.__attributes[name] = []; if (raw) { for (let i = 0, len = raw.length; i < len; i++) { this.__attributes[name].push(rawToValue(this.app, args, raw[i], old ? old[i] : null)); } } } else { this.__attributes[name] = rawToValue(this.app, args, raw, old); } this.fire(evt, name, this.__attributes[name], oldCopy); this.fire(evtName, this.__attributes[name], oldCopy); } }); } /** * Remove Attribute. * * @param {string} name - Name of an attribute. * @returns {boolean} True if removed or false if not defined. * @example * PlayerController.attributes.remove('fullName'); */ remove(name) { if (!this.index[name]) { return false; } delete this.index[name]; delete this.scriptType.prototype[name]; return true; } /** * Detect if Attribute is added. * * @param {string} name - Name of an attribute. * @returns {boolean} True if Attribute is defined. * @example * if (PlayerController.attributes.has('fullName')) { * // attribute fullName is defined * } */ has(name) { return !!this.index[name]; } /** * Get object with attribute arguments. Note: Changing argument properties will not affect * existing Script Instances. * * @param {string} name - Name of an attribute. * @returns {?object} Arguments with attribute properties. * @example * // changing default value for an attribute 'fullName' * var attr = PlayerController.attributes.get('fullName'); * if (attr) attr.default = 'Unknown'; */ get(name) { return this.index[name] || null; } }; __publicField(_ScriptAttributes, "assignAttributesToScript", assignAttributesToScript); __publicField(_ScriptAttributes, "attributeToValue", attributeToValue); __publicField(_ScriptAttributes, "reservedNames", /* @__PURE__ */ new Set([ "app", "entity", "enabled", "_enabled", "_enabledOld", "_destroyed", "__attributes", "__attributesRaw", "__scriptType", "__executionOrder", "_callbacks", "_callbackActive", "has", "get", "on", "off", "fire", "once", "hasEvent" ])); let ScriptAttributes = _ScriptAttributes; export { ScriptAttributes, assignAttributesToScript };