Source: core/sceneObjs/other/Protractor.js

/*
 * Copyright 2024 The Ray Optics Simulation authors and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import BaseSceneObj from '../BaseSceneObj.js';
import i18next from 'i18next';
import CircleObjMixin from '../CircleObjMixin.js';

/**
 * The protractor tool
 * 
 * Tools -> Other -> Protractor
 * @class
 * @extends BaseSceneObj
 * @memberof sceneObjs
 * @property {Point} p1 - The center of the protractor.
 * @property {Point} p2 - The zero point on the protractor.
 */
class Protractor extends CircleObjMixin(BaseSceneObj) {
  static type = 'Protractor';
  static serializableDefaults = {
    p1: null,
    p2: null
  };

  populateObjBar(objBar) {
    objBar.setTitle(i18next.t('main:tools.Protractor.title'));
  }

  draw(canvasRenderer, isAboveLight, isHovered) {
    const ctx = canvasRenderer.ctx;
    const ls = canvasRenderer.lengthScale;

    if (!isAboveLight) {
      ctx.globalCompositeOperation = 'lighter';
      var r = Math.sqrt((this.p2.x - this.p1.x) * (this.p2.x - this.p1.x) + (this.p2.y - this.p1.y) * (this.p2.y - this.p1.y));
      var scale_width_limit = 5 * ls;

      var scale_step = 1;
      var scale_step_mid = 5;
      var scale_step_long = 10;
      var scale_len = 10 * ls;
      var scale_len_mid = 15 * ls;
      var scale_len_long = 20 * ls;

      ctx.strokeStyle = isHovered ? 'cyan' : ('rgb(128,128,128)');
      ctx.font = 'bold ' + (14 * ls) + 'px Arial';
      ctx.fillStyle = 'rgb(128,128,128)';

      if (r * scale_step * Math.PI / 180 < scale_width_limit) {
        // The scale is too small
        scale_step = 2;
        scale_step_mid = 10;
        scale_step_long = 30;
      }
      if (r * scale_step * Math.PI / 180 < scale_width_limit) {
        // The scale is too small
        scale_step = 5;
        scale_step_mid = 10;
        scale_step_long = 30;
        scale_len = 5 * ls;
        scale_len_mid = 8 * ls;
        scale_len_long = 10 * ls;
        ctx.font = 'bold ' + (10 * ls) + 'px Arial';
      }
      if (r * scale_step * Math.PI / 180 < scale_width_limit) {
        // The scale is too small
        scale_step = 10;
        scale_step_mid = 30;
        scale_step_long = 90;
      }

      ctx.textAlign = 'center';
      ctx.textBaseline = 'top';

      ctx.lineWidth = 1 * ls;

      ctx.beginPath();
      ctx.arc(this.p1.x, this.p1.y, r, 0, Math.PI * 2, false);

      var ang, x, y;
      for (var i = 0; i < 360; i += scale_step) {
        ang = i * Math.PI / 180 + Math.atan2(this.p2.y - this.p1.y, this.p2.x - this.p1.x);
        ctx.moveTo(this.p1.x + r * Math.cos(ang), this.p1.y + r * Math.sin(ang));
        if (i % scale_step_long == 0) {
          x = this.p1.x + (r - scale_len_long) * Math.cos(ang);
          y = this.p1.y + (r - scale_len_long) * Math.sin(ang);
          ctx.lineTo(x, y);
          if (canvasRenderer.isSVG) ctx.stroke();
          ctx.save();
          ctx.translate(x, y);
          ctx.rotate(ang + Math.PI * 0.5);
          ctx.fillText((i > 180) ? (360 - i) : i, 0, 0);
          ctx.restore();
        } else if (i % scale_step_mid == 0) {
          ctx.lineTo(this.p1.x + (r - scale_len_mid) * Math.cos(ang), this.p1.y + (r - scale_len_mid) * Math.sin(ang));
        } else {
          ctx.lineTo(this.p1.x + (r - scale_len) * Math.cos(ang), this.p1.y + (r - scale_len) * Math.sin(ang));
        }
      }
      ctx.stroke();

      ctx.globalCompositeOperation = 'source-over';
    }
    ctx.fillStyle = 'red';
    ctx.fillRect(this.p1.x - 1.5 * ls, this.p1.y - 1.5 * ls, 3 * ls, 3 * ls);
    if (isHovered) {
      ctx.fillStyle = 'magenta';
      ctx.fillRect(this.p2.x - 2.5 * ls, this.p2.y - 2.5 * ls, 5 * ls, 5 * ls);
    }
  }
};

export default Protractor;