【问题标题】:Unable to display text sprites in Autodesk Forge Viewer无法在 Autodesk Forge 查看器中显示文本精灵
【发布时间】:2021-02-06 10:14:22
【问题描述】:

我无法在 Autodesk Forge 查看器中显示文本精灵。

我创建了一个示例项目ForgeTextSprite,它在加载的模型下方显示一个罗盘。虽然指南针的度数显示正常,但不显示红衣主教(N、S、E、W)的文字。

下面的 TextSprite.ts 文件已从 three-spritetext 转换为 Typescript。我注释掉了 THREE.LinearFilter 的用法,因为 Forge 查看器中似乎没有。

// Converted to Typescript from https://github.com/vasturiano/three-spritetext.
//
// Commented out usage to THREE.LinearFilter as that doesn't seem to be present in the
// Forge viewer.

export default class TextSprite extends THREE.Sprite {
  _text: string;
  _textHeight: number;
  _color: string;
  _backgroundColor: string;
  _padding: number;
  _borderWidth: number;
  _borderColor: string;
  _strokeWidth: number;
  _strokeColor: string;
  _fontFace: string;
  _fontSize: number;
  _fontWeight: string;
  _canvas: HTMLCanvasElement;
  _texture: THREE.Texture;

  constructor(text = '', textHeight = 10, color = 'rgba(255, 255, 255, 1)') {
    super(new THREE.SpriteMaterial({ map: new THREE.Texture() }));

    this._text = `${text}`;
    this._textHeight = textHeight;
    this._color = color;
    this._backgroundColor = ''; // no background color

    this._padding = 0;
    this._borderWidth = 0;
    this._borderColor = 'blue';

    this._strokeWidth = 0;
    this._strokeColor = 'green';

    this._fontFace = 'Arial';
    this._fontSize = 90; // defines text resolution
    this._fontWeight = 'normal';

    this._canvas = document.createElement('canvas');

    if (this.material instanceof THREE.SpriteMaterial) {
      this._texture = this.material.map;
    }
    // this._texture.minFilter = three.LinearFilter;

    this._genCanvas();
  }

  get text() { return this._text; }
  set text(text) { this._text = text; this._genCanvas(); }
  get textHeight() { return this._textHeight; }
  set textHeight(textHeight) { this._textHeight = textHeight; this._genCanvas(); }
  get color() { return this._color; }
  set color(color) { this._color = color; this._genCanvas(); }
  get backgroundColor() { return this._backgroundColor; }
  set backgroundColor(color) { this._backgroundColor = color; this._genCanvas(); }
  get padding() { return this._padding; }
  set padding(padding) { this._padding = padding; this._genCanvas(); }
  get borderWidth() { return this._borderWidth; }
  set borderWidth(borderWidth) { this._borderWidth = borderWidth; this._genCanvas(); }
  get borderColor() { return this._borderColor; }
  set borderColor(borderColor) { this._borderColor = borderColor; this._genCanvas(); }
  get fontFace() { return this._fontFace; }
  set fontFace(fontFace) { this._fontFace = fontFace; this._genCanvas(); }
  get fontSize() { return this._fontSize; }
  set fontSize(fontSize) { this._fontSize = fontSize; this._genCanvas(); }
  get fontWeight() { return this._fontWeight; }
  set fontWeight(fontWeight) { this._fontWeight = fontWeight; this._genCanvas(); }
  get strokeWidth() { return this._strokeWidth; }
  set strokeWidth(strokeWidth) { this._strokeWidth = strokeWidth; this._genCanvas(); }
  get strokeColor() { return this._strokeColor; }
  set strokeColor(strokeColor) { this._strokeColor = strokeColor; this._genCanvas(); }

  _genCanvas() {
    const canvas = this._canvas;
    const ctx = canvas.getContext('2d');

    const border = [this.borderWidth, this.borderWidth]; // x,y border
    const relBorder = border.map(b => b * this.fontSize * 0.1); // border in canvas units

    const padding = [this.padding, this.padding]; // x,y padding
    const relPadding = padding.map(p => p * this.fontSize * 0.1); // padding in canvas units

    const lines = this.text.split('\n');
    const font = `${this.fontWeight} ${this.fontSize}px ${this.fontFace}`;

    ctx.font = font; // measure canvas with appropriate font
    const innerWidth = Math.max(...lines.map(line => ctx.measureText(line).width));
    const innerHeight = this.fontSize * lines.length;
    canvas.width = innerWidth + relBorder[0] * 2 + relPadding[0] * 2;
    canvas.height = innerHeight + relBorder[1] * 2 + relPadding[1] * 2;

    // paint border
    if (this.borderWidth) {
      ctx.strokeStyle = this.borderColor;

      if (relBorder[0]) {
        ctx.lineWidth = relBorder[0] * 2;
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(0, canvas.height);
        ctx.moveTo(canvas.width, 0);
        ctx.lineTo(canvas.width, canvas.height);
        ctx.stroke();
      }

      if (relBorder[1]) {
        ctx.lineWidth = relBorder[1] * 2;
        ctx.beginPath();
        ctx.moveTo(relBorder[0], 0);
        ctx.lineTo(canvas.width - relBorder[0], 0);
        ctx.moveTo(relBorder[0], canvas.height);
        ctx.lineTo(canvas.width - relBorder[0], canvas.height);
        ctx.stroke();
      }
    }

    ctx.translate(relBorder[0], relBorder[1]);

    // paint background
    if (this.backgroundColor) {
      ctx.fillStyle = this.backgroundColor;
      ctx.fillRect(0, 0, canvas.width - relBorder[0] * 2, canvas.height - relBorder[1] * 2);
    }

    ctx.translate(relPadding[0], relPadding[1]);

    // paint text
    ctx.font = font; // Set font again after canvas is resized, as context properties are reset
    ctx.fillStyle = this.color;
    ctx.textBaseline = 'bottom';

    const drawTextStroke = this.strokeWidth > 0;
    if (drawTextStroke) {
      ctx.lineWidth = this.strokeWidth * this.fontSize / 10;
      ctx.strokeStyle = this.strokeColor;
    }

    lines.forEach((line, index) => {
      const lineX = (innerWidth - ctx.measureText(line).width) / 2;
      const lineY = (index + 1) * this.fontSize;

      drawTextStroke && ctx.strokeText(line, lineX, lineY);
      ctx.fillText(line, lineX, lineY);
    });

    // Inject canvas into sprite
    this._texture.image = canvas;
    this._texture.needsUpdate = true;

    const yScale = this.textHeight * lines.length + border[1] * 2 + padding[1] * 2;
    this.scale.set(yScale * canvas.width / canvas.height, yScale, 0);
  }

  // clone(recursive?: boolean): this {
  //   return new SpriteText(this.text, this.textHeight, this.color).copy(this);
  // }

  copy(source: this, recursive?: boolean): this {
    super.copy(source, recursive);

    this.color = source.color;
    this.backgroundColor = source.backgroundColor;
    this.padding = source.padding;
    this.borderWidth = source.borderWidth;
    this.borderColor = source.borderColor;
    this.fontFace = source.fontFace;
    this.fontSize = source.fontSize;
    this.fontWeight = source.fontWeight;
    this.strokeWidth = source.strokeWidth;
    this.strokeColor = source.strokeColor;

    return this;
  }
}

在 CompassRose.ts 中,文本精灵被创建、翻译和旋转到适当的位置,然后添加到 THREE.Group。

import TextSprite from './TextSprite';

const markerLength = 1;
const fifthMarkerLength = 5;
const tenthMarkerLength = 10;
const ordinalMarkerLength = 15;

const color = 0x444444;
const fontSize = 4;
const halfFontSize = fontSize / 2;
const radius = 100;

const cardinalOuter = radius + 10;


export default class CompassRose {
    build(): THREE.Group {
        const group = new THREE.Group();

        const material = new THREE.LineBasicMaterial({
            color: color
        });

        for (let degree = 0; degree < 360; degree++) {
            const lineParent = new THREE.Group();
            const lineAngle = (Math.PI * degree) / 180;

            lineParent.rotateX(lineAngle);

            const length = this.getMarkerLength(degree);
            const line = this.buildLine(radius, length, material);

            lineParent.add(line);
            group.add(lineParent);
        }

        let sprite = this.createHeadingTextSprite("N")
            .translateX(cardinalOuter - fontSize)
            .translateY(halfFontSize)
            .rotateZ(-Math.PI / 2);
        group.add(sprite);

        sprite = this.createHeadingTextSprite("S")
            .translateX(-cardinalOuter)
            .translateY(halfFontSize)
            .rotateZ(-Math.PI / 2);
        group.add(sprite);

        sprite = this.createHeadingTextSprite("W")
            .translateY(cardinalOuter)
            .translateX(-halfFontSize)
            .rotateZ(-Math.PI / 2);
        group.add(sprite);

        sprite = this.createHeadingTextSprite("E")
            .translateY(-cardinalOuter + fontSize)
            .translateX(-halfFontSize)
            .rotateZ(-Math.PI / 2);
        group.add(sprite);

        return group;
    }

    private getMarkerLength(degree: number) {
        let length: number;

        if (degree % 90 === 0) {
            length = ordinalMarkerLength;
        } else if (degree % 10 === 0) {
            length = tenthMarkerLength;
        } else if (degree % 5 === 0) {
            length = fifthMarkerLength;
        } else {
            length = markerLength;
        }

        return length;
    }

    private buildLine(radius: number, length: number, material: THREE.Material) {
        const lineGeometry = new THREE.Geometry();
        lineGeometry.vertices.push(new THREE.Vector3(0, radius, 0));
        lineGeometry.vertices.push(new THREE.Vector3(0, radius - length, 0));
        return new THREE.Line(lineGeometry, material);
    }

    private createHeadingTextSprite(text: string): THREE.Sprite {
        return new TextSprite(text, 10, '#888888');
    }
}

浏览器控制台窗口中不显示任何错误。

【问题讨论】:

    标签: autodesk-forge autodesk-viewer


    【解决方案1】:

    请注意,Forge Viewer 基于 Three.js 版本 R71。在您的代码中,您似乎使用了 Three.js R103 的类型定义,并且三个 spritetext 依赖项引用了 Three.js R86。恐怕是这些版本的差异导致渲染文本精灵时静默失败。

    作为替代方案,我建议:

    • 改用THREE.TextGeometry(R71 中提供)生成细长的 3D 文本
    • 在场景中添加一个简单的THREE.PlaneBufferGeometry,并将刻度盘和标签添加为纹理
    • 考虑将表盘/标签作为另一个 Forge 模型(例如,从 PDF 或从 DWG 文件)加载到查看器场景中

    【讨论】:

    • 感谢您的快速回复!我刚刚更新了项目以删除对 Three.js R103 类型定义的依赖。没有对三精灵文本的依赖,我已将代码转换为 Typescript 并将源文件添加到项目中,请参阅TextSprite.ts。 Three.js R71 支持精灵,所以我希望这个版本的文本精灵可以工作?
    • 是的,它可以工作,但是 Three.js API 变化很快,所以即使版本 R71 确实支持精灵,但在版本之前可能会有很多变化R86(似乎已经实现了文本精灵代码)。我们在 Forge Viewer 中尝试使用 potree 时遇到了类似的问题。
    • 所以底线是:我们不能正式支持针对不同版本的three.js 编写的库。如果需要使此类库与 Forge Viewer 兼容,我们需要分配一些时间来研究、调试和移植代码,就像我们对 Potree 所做的那样。
    • 我原以为会需要更容易添加文本/标签,有什么地方可以提出要求吗?另外,如何加载字体以与 THREE.TextGeometry 一起使用?如果我导入字体``` import FontJson from '../fonts/helvetiker_regular.typeface.json' ``` 然后创建它 ``` const font = new THREE.Font(FontJson);常量选项 = { 字体:字体 }; const textGeometry = new THREE.TextGeometry(text, options); const textMesh = new THREE.Mesh(textGeometry, material); ```我得到一个运行时错误:TypeError: THREE.Font is not a constructor
    • 想通了,我必须为 THREE.FontUtils 添加一个类型声明,然后我可以在初始化时调用它,例如三.FontUtils.loadFace(FontJson);我已经更新了示例项目,现在显示文本:-)
    猜你喜欢
    • 2018-09-13
    • 2020-09-06
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 2021-09-19
    • 2021-02-17
    • 2018-08-16
    • 2020-10-23
    相关资源
    最近更新 更多