UNPKG

polygonjs-engine

Version:

node-based webgl 3D engine https://polygonjs.com

272 lines (271 loc) 8.54 kB
import {TypedSopNode} from "./_Base"; import {ArrayUtils as ArrayUtils2} from "../../../core/ArrayUtils"; import {ObjectType} from "../../../core/geometry/Constant"; import {TextBufferGeometry as TextBufferGeometry2} from "three/src/geometries/TextBufferGeometry"; import {BufferGeometry as BufferGeometry2} from "three/src/core/BufferGeometry"; import {ShapeBufferGeometry as ShapeBufferGeometry2} from "three/src/geometries/ShapeBufferGeometry"; import {FontLoader as FontLoader2} from "three/src/loaders/FontLoader"; import {Float32BufferAttribute} from "three/src/core/BufferAttribute"; import {BufferGeometryUtils as BufferGeometryUtils2} from "../../../modules/three/examples/jsm/utils/BufferGeometryUtils"; import {ModuleName} from "../../poly/registers/modules/_BaseRegister"; import {Poly as Poly2} from "../../Poly"; import {DEMO_ASSETS_ROOT_URL} from "../../../core/Assets"; const DEFAULT_FONT_URL = `${DEMO_ASSETS_ROOT_URL}/fonts/droid_sans_regular.typeface.json`; export var TEXT_TYPE; (function(TEXT_TYPE2) { TEXT_TYPE2["MESH"] = "mesh"; TEXT_TYPE2["FLAT"] = "flat"; TEXT_TYPE2["LINE"] = "line"; TEXT_TYPE2["STROKE"] = "stroke"; })(TEXT_TYPE || (TEXT_TYPE = {})); export const TEXT_TYPES = [TEXT_TYPE.MESH, TEXT_TYPE.FLAT, TEXT_TYPE.LINE, TEXT_TYPE.STROKE]; const GENERATION_ERROR_MESSAGE = `failed to generate geometry. Try to remove some characters`; import {NodeParamsConfig, ParamConfig} from "../utils/params/ParamsConfig"; class TextSopParamsConfig extends NodeParamsConfig { constructor() { super(...arguments); this.font = ParamConfig.STRING(DEFAULT_FONT_URL); this.text = ParamConfig.STRING("polygonjs", {multiline: true}); this.type = ParamConfig.INTEGER(0, { menu: { entries: TEXT_TYPES.map((type, i) => { return { name: type, value: i }; }) } }); this.size = ParamConfig.FLOAT(1, { range: [0, 1], rangeLocked: [true, false] }); this.extrude = ParamConfig.FLOAT(0.1, { visibleIf: { type: TEXT_TYPES.indexOf(TEXT_TYPE.MESH) } }); this.segments = ParamConfig.INTEGER(1, { range: [1, 20], rangeLocked: [true, false], visibleIf: { type: TEXT_TYPES.indexOf(TEXT_TYPE.MESH) } }); this.strokeWidth = ParamConfig.FLOAT(0.02, { visibleIf: { type: TEXT_TYPES.indexOf(TEXT_TYPE.STROKE) } }); } } const ParamsConfig2 = new TextSopParamsConfig(); export class TextSopNode extends TypedSopNode { constructor() { super(...arguments); this.params_config = ParamsConfig2; this._font_loader = new FontLoader2(); this._loaded_fonts = {}; } static type() { return "text"; } initializeNode() { } async cook() { try { this._loaded_fonts[this.pv.font] = this._loaded_fonts[this.pv.font] || await this._load_url(); } catch (err) { this.states.error.set(`count not load font (${this.pv.font})`); return; } const font = this._loaded_fonts[this.pv.font]; if (font) { switch (TEXT_TYPES[this.pv.type]) { case TEXT_TYPE.MESH: return this._create_geometry_from_type_mesh(font); case TEXT_TYPE.FLAT: return this._create_geometry_from_type_flat(font); case TEXT_TYPE.LINE: return this._create_geometry_from_type_line(font); case TEXT_TYPE.STROKE: return this._create_geometry_from_type_stroke(font); default: console.warn("type is not valid"); } } } _create_geometry_from_type_mesh(font) { const text = this.displayed_text(); const parameters = { font, size: this.pv.size, height: this.pv.extrude, curveSegments: this.pv.segments }; try { const geometry = new TextBufferGeometry2(text, parameters); if (!geometry.index) { const position_array = geometry.getAttribute("position").array; geometry.setIndex(ArrayUtils2.range(position_array.length / 3)); } this.setGeometry(geometry); } catch (err) { this.states.error.set(GENERATION_ERROR_MESSAGE); } } _create_geometry_from_type_flat(font) { const shapes = this._get_shapes(font); if (shapes) { var geometry = new ShapeBufferGeometry2(shapes); this.setGeometry(geometry); } } _create_geometry_from_type_line(font) { const shapes = this.shapes_from_font(font); if (shapes) { const positions = []; const indices = []; let current_index = 0; for (let i = 0; i < shapes.length; i++) { const shape = shapes[i]; const points = shape.getPoints(); for (let j = 0; j < points.length; j++) { const point = points[j]; positions.push(point.x); positions.push(point.y); positions.push(0); indices.push(current_index); if (j > 0 && j < points.length - 1) { indices.push(current_index); } current_index += 1; } } const geometry = new BufferGeometry2(); geometry.setAttribute("position", new Float32BufferAttribute(positions, 3)); geometry.setIndex(indices); this.setGeometry(geometry, ObjectType.LINE_SEGMENTS); } } async _create_geometry_from_type_stroke(font) { const shapes = this.shapes_from_font(font); if (shapes) { const loader = await this._load_svg_loader(); if (!loader) { return; } var style = loader.getStrokeStyle(this.pv.strokeWidth, "white", "miter", "butt", 4); const geometries = []; for (let i = 0; i < shapes.length; i++) { const shape = shapes[i]; const points = shape.getPoints(); const arcDivisions = 12; const minDistance = 1e-3; const geometry = loader.pointsToStroke(points, style, arcDivisions, minDistance); geometries.push(geometry); } const merged_geometry = BufferGeometryUtils2.mergeBufferGeometries(geometries); this.setGeometry(merged_geometry); } } shapes_from_font(font) { const shapes = this._get_shapes(font); if (shapes) { const holeShapes = []; for (let i = 0; i < shapes.length; i++) { const shape = shapes[i]; if (shape.holes && shape.holes.length > 0) { for (let j = 0; j < shape.holes.length; j++) { const hole = shape.holes[j]; holeShapes.push(hole); } } } shapes.push.apply(shapes, holeShapes); return shapes; } } _get_shapes(font) { const text = this.displayed_text(); try { const shapes = font.generateShapes(text, this.pv.size); return shapes; } catch (err) { this.states.error.set(GENERATION_ERROR_MESSAGE); } } displayed_text() { return this.pv.text || ""; } _load_url() { const url = `${this.pv.font}?${Date.now()}`; const ext = this.get_extension(); switch (ext) { case "ttf": { return this._load_ttf(url); } case "json": { return this._load_json(url); } default: { return null; } } } async requiredModules() { if (this.p.font.isDirty()) { await this.p.font.compute(); } const ext = this.get_extension(); switch (ext) { case "ttf": { return [ModuleName.TTFLoader]; } case "json": { return [ModuleName.SVGLoader]; } } } get_extension() { const url = this.pv.font; const elements1 = url.split("?")[0]; const elements2 = elements1.split("."); return elements2[elements2.length - 1]; } _load_ttf(url) { return new Promise(async (resolve, reject) => { const loaded_module = await this._load_ttf_loader(); if (!loaded_module) { return; } loaded_module.load(url, (fnt) => { const parsed = this._font_loader.parse(fnt); resolve(parsed); }, void 0, () => { reject(); }); }); } _load_json(url) { return new Promise((resolve, reject) => { this._font_loader.load(url, (font) => { resolve(font); }, void 0, () => { reject(); }); }); } async _load_ttf_loader() { const module = await Poly2.modulesRegister.module(ModuleName.TTFLoader); if (module) { return new module.TTFLoader(); } } async _load_svg_loader() { const module = await Poly2.modulesRegister.module(ModuleName.SVGLoader); if (module) { return module.SVGLoader; } } }