Newer
Older
AegisforEcosystem / next / AR.js-3.4.0 / three.js / src / markers-area / arjs-markersareacontrols.js
@KAOKA Daisuke KAOKA Daisuke on 31 May 2022 11 KB into AR.js
import * as THREE from "three";
import ArBaseControls from "../threex/threex-arbasecontrols";
import ArMarkerControls from "../threex/arjs-markercontrols";
import ArMultiMarkerControls from "./arjs-markersareacontrols";

const MarkersAreaControls = function (arToolkitContext, object3d, parameters) {
  var _this = this;
  ArBaseControls.call(this, object3d);

  if (arguments.length > 3)
    console.assert("wrong api for", MarkersAreaControls);

  // have a parameters in argument
  this.parameters = {
    // list of controls for each subMarker
    subMarkersControls: parameters.subMarkersControls,
    // list of pose for each subMarker relative to the origin
    subMarkerPoses: parameters.subMarkerPoses,
    // change matrix mode - [modelViewMatrix, cameraTransformMatrix]
    changeMatrixMode:
      parameters.changeMatrixMode !== undefined
        ? parameters.changeMatrixMode
        : "modelViewMatrix",
  };

  this.object3d.visible = false;
  // honor obsolete stuff - add a warning to use
  this.subMarkersControls = this.parameters.subMarkersControls;
  this.subMarkerPoses = this.parameters.subMarkerPoses;

  // listen to arToolkitContext event 'sourceProcessed'
  // - after we fully processed one image, aka when we know all detected poses in it
  arToolkitContext.addEventListener("sourceProcessed", function () {
    _this._onSourceProcessed();
  });
};

MarkersAreaControls.prototype = Object.create(ArBaseControls.prototype);
MarkersAreaControls.prototype.constructor = MarkersAreaControls;

//////////////////////////////////////////////////////////////////////////////
//		Code Separator
//////////////////////////////////////////////////////////////////////////////

/**
 * What to do when a image source is fully processed
 */
MarkersAreaControls.prototype._onSourceProcessed = function () {
  var _this = this;
  var stats = {
    count: 0,
    position: {
      sum: new THREE.Vector3(0, 0, 0),
      average: new THREE.Vector3(0, 0, 0),
    },
    quaternion: {
      sum: new THREE.Quaternion(0, 0, 0, 0),
      average: new THREE.Quaternion(0, 0, 0, 0),
    },
    scale: {
      sum: new THREE.Vector3(0, 0, 0),
      average: new THREE.Vector3(0, 0, 0),
    },
  };

  var firstQuaternion =
    _this.parameters.subMarkersControls[0].object3d.quaternion;

  this.parameters.subMarkersControls.forEach(function (
    markerControls,
    markerIndex
  ) {
    var markerObject3d = markerControls.object3d;
    // if this marker is not visible, ignore it
    if (markerObject3d.visible === false) return;

    // transformation matrix of this.object3d according to this sub-markers
    var matrix = markerObject3d.matrix.clone();
    var markerPose = _this.parameters.subMarkerPoses[markerIndex];
    matrix.multiply(new THREE.Matrix4().getInverse(markerPose));

    // decompose the matrix into .position, .quaternion, .scale
    var position = new THREE.Vector3();
    var quaternion = new THREE.Quaternion();
    var scale = new THREE.Vector3();
    matrix.decompose(position, quaternion, scale);

    // http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
    stats.count++;

    MarkersAreaControls.averageVector3(
      stats.position.sum,
      position,
      stats.count,
      stats.position.average
    );
    MarkersAreaControls.averageQuaternion(
      stats.quaternion.sum,
      quaternion,
      firstQuaternion,
      stats.count,
      stats.quaternion.average
    );
    MarkersAreaControls.averageVector3(
      stats.scale.sum,
      scale,
      stats.count,
      stats.scale.average
    );
  });

  // honor _this.object3d.visible
  if (stats.count > 0) {
    _this.object3d.visible = true;
  } else {
    _this.object3d.visible = false;
  }

  // if at least one sub-marker has been detected, make the average of all detected markers
  if (stats.count > 0) {
    // compute modelViewMatrix
    var modelViewMatrix = new THREE.Matrix4();
    modelViewMatrix.compose(
      stats.position.average,
      stats.quaternion.average,
      stats.scale.average
    );

    // change _this.object3d.matrix based on parameters.changeMatrixMode
    if (this.parameters.changeMatrixMode === "modelViewMatrix") {
      _this.object3d.matrix.copy(modelViewMatrix);
    } else if (this.parameters.changeMatrixMode === "cameraTransformMatrix") {
      _this.object3d.matrix.getInverse(modelViewMatrix);
    } else {
      console.assert(false);
    }

    // decompose - the matrix into .position, .quaternion, .scale
    _this.object3d.matrix.decompose(
      _this.object3d.position,
      _this.object3d.quaternion,
      _this.object3d.scale
    );
  }
};

//////////////////////////////////////////////////////////////////////////////
//		Utility functions
//////////////////////////////////////////////////////////////////////////////

/**
 * from http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
 */
MarkersAreaControls.averageQuaternion = function (
  quaternionSum,
  newQuaternion,
  firstQuaternion,
  count,
  quaternionAverage
) {
  quaternionAverage = quaternionAverage || new THREE.Quaternion();
  // sanity check
  console.assert(firstQuaternion instanceof THREE.Quaternion === true);

  // from http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
  if (newQuaternion.dot(firstQuaternion) > 0) {
    newQuaternion = new THREE.Quaternion(
      -newQuaternion.x,
      -newQuaternion.y,
      -newQuaternion.z,
      -newQuaternion.w
    );
  }

  quaternionSum.x += newQuaternion.x;
  quaternionSum.y += newQuaternion.y;
  quaternionSum.z += newQuaternion.z;
  quaternionSum.w += newQuaternion.w;

  quaternionAverage.x = quaternionSum.x / count;
  quaternionAverage.y = quaternionSum.y / count;
  quaternionAverage.z = quaternionSum.z / count;
  quaternionAverage.w = quaternionSum.w / count;

  quaternionAverage.normalize();

  return quaternionAverage;
};

MarkersAreaControls.averageVector3 = function (
  vector3Sum,
  vector3,
  count,
  vector3Average
) {
  vector3Average = vector3Average || new THREE.Vector3();

  vector3Sum.x += vector3.x;
  vector3Sum.y += vector3.y;
  vector3Sum.z += vector3.z;

  vector3Average.x = vector3Sum.x / count;
  vector3Average.y = vector3Sum.y / count;
  vector3Average.z = vector3Sum.z / count;

  return vector3Average;
};

//////////////////////////////////////////////////////////////////////////////
//		Utility function
//////////////////////////////////////////////////////////////////////////////

/**
 * compute the center of this multimarker file
 */
MarkersAreaControls.computeCenter = function (jsonData) {
  var multiMarkerFile = JSON.parse(jsonData);
  var stats = {
    count: 0,
    position: {
      sum: new THREE.Vector3(0, 0, 0),
      average: new THREE.Vector3(0, 0, 0),
    },
    quaternion: {
      sum: new THREE.Quaternion(0, 0, 0, 0),
      average: new THREE.Quaternion(0, 0, 0, 0),
    },
    scale: {
      sum: new THREE.Vector3(0, 0, 0),
      average: new THREE.Vector3(0, 0, 0),
    },
  };
  var firstQuaternion = new THREE.Quaternion(); // FIXME ???

  multiMarkerFile.subMarkersControls.forEach(function (item) {
    var poseMatrix = new THREE.Matrix4().fromArray(item.poseMatrix);

    var position = new THREE.Vector3();
    var quaternion = new THREE.Quaternion();
    var scale = new THREE.Vector3();
    poseMatrix.decompose(position, quaternion, scale);

    // http://wiki.unity3d.com/index.php/Averaging_Quaternions_and_Vectors
    stats.count++;

    MarkersAreaControls.averageVector3(
      stats.position.sum,
      position,
      stats.count,
      stats.position.average
    );
    MarkersAreaControls.averageQuaternion(
      stats.quaternion.sum,
      quaternion,
      firstQuaternion,
      stats.count,
      stats.quaternion.average
    );
    MarkersAreaControls.averageVector3(
      stats.scale.sum,
      scale,
      stats.count,
      stats.scale.average
    );
  });

  var averageMatrix = new THREE.Matrix4();
  averageMatrix.compose(
    stats.position.average,
    stats.quaternion.average,
    stats.scale.average
  );

  return averageMatrix;
};

MarkersAreaControls.computeBoundingBox = function (jsonData) {
  var multiMarkerFile = JSON.parse(jsonData);
  var boundingBox = new THREE.Box3();

  multiMarkerFile.subMarkersControls.forEach(function (item) {
    var poseMatrix = new THREE.Matrix4().fromArray(item.poseMatrix);

    var position = new THREE.Vector3();
    var quaternion = new THREE.Quaternion();
    var scale = new THREE.Vector3();
    poseMatrix.decompose(position, quaternion, scale);

    boundingBox.expandByPoint(position);
  });

  return boundingBox;
};
//////////////////////////////////////////////////////////////////////////////
//		updateSmoothedControls
//////////////////////////////////////////////////////////////////////////////

MarkersAreaControls.prototype.updateSmoothedControls = function (
  smoothedControls,
  lerpsValues
) {
  // handle default values
  if (lerpsValues === undefined) {
    // FIXME this parameter format is uselessly cryptic
    // lerpValues = [
    // {lerpPosition: 0.5, lerpQuaternion: 0.2, lerpQuaternion: 0.7}
    // ]
    lerpsValues = [
      [0.3 + 0.1, 0.1, 0.3],
      [0.4 + 0.1, 0.1, 0.4],
      [0.4 + 0.1, 0.2, 0.5],
      [0.5 + 0.1, 0.2, 0.7],
      [0.5 + 0.1, 0.2, 0.7],
    ];
  }
  // count how many subMarkersControls are visible
  var nVisible = 0;
  this.parameters.subMarkersControls.forEach(function (
    markerControls,
    markerIndex
  ) {
    var markerObject3d = markerControls.object3d;
    if (markerObject3d.visible === true) nVisible++;
  });

  // find the good lerpValues
  if (lerpsValues[nVisible - 1] !== undefined) {
    var lerpValues = lerpsValues[nVisible - 1];
  } else {
    var lerpValues = lerpsValues[lerpsValues.length - 1];
  }

  // modify lerpValues in smoothedControls
  smoothedControls.parameters.lerpPosition = lerpValues[0];
  smoothedControls.parameters.lerpQuaternion = lerpValues[1];
  smoothedControls.parameters.lerpScale = lerpValues[2];
};

//////////////////////////////////////////////////////////////////////////////
//		Create ArMultiMarkerControls from JSON
//////////////////////////////////////////////////////////////////////////////

MarkersAreaControls.fromJSON = function (
  arToolkitContext,
  parent3D,
  markerRoot,
  jsonData,
  parameters
) {
  var multiMarkerFile = JSON.parse(jsonData);
  // declare variables
  var subMarkersControls = [];
  var subMarkerPoses = [];
  // handle default arguments
  parameters = parameters || {};

  // prepare the parameters
  multiMarkerFile.subMarkersControls.forEach(function (item) {
    // create a markerRoot
    var markerRoot = new THREE.Object3D();
    parent3D.add(markerRoot);

    // create markerControls for our markerRoot
    var subMarkerControls = new ArMarkerControls(
      arToolkitContext,
      markerRoot,
      item.parameters
    );

    // if( true ){
    // store it in the parameters
    subMarkersControls.push(subMarkerControls);
    subMarkerPoses.push(new THREE.Matrix4().fromArray(item.poseMatrix));
    // }else{
    // 		// build a smoothedControls
    // 		var smoothedRoot = new THREE.Group()
    // 		parent3D.add(smoothedRoot)
    // 		var smoothedControls = new THREEx.ArSmoothedControls(smoothedRoot, {
    // 			lerpPosition : 0.1,
    // 			lerpQuaternion : 0.1,
    // 			lerpScale : 0.1,
    // 			minVisibleDelay: 0,
    // 			minUnvisibleDelay: 0,
    // 		})
    // 		onRenderFcts.push(function(delta){
    // 			smoothedControls.update(markerRoot)	// TODO this is a global
    // 		})
    //
    //
    // 		// store it in the parameters
    // 		subMarkersControls.push(smoothedControls)
    // 		subMarkerPoses.push(new THREE.Matrix4().fromArray(item.poseMatrix))
    // }
  });

  parameters.subMarkersControls = subMarkersControls;
  parameters.subMarkerPoses = subMarkerPoses;
  // create a new ArMultiMarkerControls
  var multiMarkerControls = new ArMultiMarkerControls(
    arToolkitContext,
    markerRoot,
    parameters
  );

  // return it
  return multiMarkerControls;
};

export default MarkersAreaControls;