/* global THREE */
const base = require("../base");
require("./visual");
const ripe = base.ripe;
/**
* Binds an PRC Configurator to this Ripe instance.
*
* @param {Configurator} element The PRC Configurator to be used by the Ripe instance.
* @param {Object} options An Object with options to configure the Configurator instance.
* @returns {Configurator} The Configurator instance created.
*/
ripe.Ripe.prototype.bindConfiguratorCsr = function(element, options = {}) {
const config = new ripe.ConfiguratorCsr(this, element, options);
return this.bindInteractable(config);
};
/**
* @class
* @classdesc Class that defines an interactive Configurator instance to be
* used in connection with the main Ripe owner to provide an
* interactive configuration experience inside a DOM.
*
* @param {Object} owner The owner (customizer instance) for
* this configurator.
* @param {Object} element The DOM element that is considered to
* be the target for the configurator, it's going to have its own
* inner HTML changed.
* @param {Object} options The options to be used to configure the
* configurator instance to be created.
*/
ripe.ConfiguratorCsr = function(owner, element, options) {
this.type = this.type || "ConfiguratorCsr";
ripe.Visual.call(this, owner, element, options);
};
ripe.ConfiguratorCsr.prototype = ripe.build(ripe.Visual.prototype);
ripe.ConfiguratorCsr.prototype.constructor = ripe.ConfiguratorCsr;
/**
* The Configurator initializer, which is called whenever
* the Configurator is going to become active.
*
* Sets the various values for the Configurator taking into
* owner's default values.
*/
ripe.ConfiguratorCsr.prototype.init = function() {
ripe.Visual.prototype.init.call(this);
// options variables
this.id = this.options.id || 0;
this.width = this.options.width || null;
this.height = this.options.height || null;
this.size = this.options.size || null;
this.pixelRatio =
this.options.pixelRatio || (typeof window !== "undefined" && window.devicePixelRatio) || 2;
this.sensitivity = this.options.sensitivity || 40;
this.verticalThreshold = this.options.verticalThreshold || 15;
this.duration = this.options.duration || 500;
this.awaitPostConfig = this.options.awaitPostConfig || false;
this.debug = this.options.debug || false;
const debugOpts = this.options.debugOptions || {};
const renderedInitialsOpts = debugOpts.renderedInitials || {};
this.debugOptions = {
framerate: debugOpts.framerate !== undefined ? debugOpts.framerate : true,
worldAxis: debugOpts.worldAxis !== undefined ? debugOpts.worldAxis : true,
modelAxis: debugOpts.modelAxis !== undefined ? debugOpts.modelAxis : true,
renderedInitials: {
axis: renderedInitialsOpts.axis !== undefined ? renderedInitialsOpts.axis : true,
line: renderedInitialsOpts.line !== undefined ? renderedInitialsOpts.line : true,
points: renderedInitialsOpts.points !== undefined ? renderedInitialsOpts.points : true
}
};
// multiplier to adjust the CSR initials mesh scale
this.INITIALS_SCALE_MULTIPLIER = 0.01;
// general state variables
this.loading = true;
this.noDrag = false;
this.clock = null;
this.animations = [];
this.isChangeFrameAnimationRunning = false;
this._pendingOps = [];
this._postRenderCallback = null;
this._onInitialsBind = null;
this._onInitialsExtraBind = null;
this._onPreConfigBind = null;
this._onPostConfigBind = null;
// CSR variables
this.rendererOptions = null;
this.cameraOptions = null;
this.zoomOptions = null;
this.enabledInitials = null;
this.initialsOptions = null;
this.dracoLoader = null;
this.renderer = null;
this.camera = null;
this.scene = null;
this.environmentTexture = null;
this.modelGroup = null;
this.mesh = null;
// CSR initials variables
this.initialsRefs = {
renderedInitials: null,
mesh: null,
baseTexture: null,
displacementTexture: null,
metallicTexture: null,
normalTexture: null,
roughnessTexture: null
};
// CSR debug variables
this.debugRefs = {
framerate: null,
worldAxis: null,
modelAxis: null,
renderedInitials: {
group: null,
axis: null,
line: null,
points: []
}
};
// handlers variables
this.isMouseDown = false;
this.referenceX = null;
this.referenceY = null;
this.prevPercentX = 0;
this.prevPercentY = 0;
// creates the necessary DOM elements and runs the
// CSR initializer
this._initLayout();
this._initCsr();
this._unregisterConfigGlobalHandlers();
this._registerConfigGlobalHandlers();
};
/**
* The Configurator deinitializer, to be called (by the owner) when
* it should stop responding to updates so that any necessary
* cleanup operations can be executed.
*/
ripe.ConfiguratorCsr.prototype.deinit = async function() {
this._deinitCsr();
this._unregisterConfigGlobalHandlers();
while (this.element && this.element.firstChild) {
this.element.removeChild(this.element.firstChild);
}
this._removeElementHandlers();
ripe.Visual.prototype.deinit.call(this);
};
/**
* Updates configurator current options with the ones provided.
*
* @param {Object} options Set of optional parameters to adjust the Configurator.
* @param {Boolean} update If an update operation should be executed after
* the options updated operation has been performed.
*/
ripe.ConfiguratorCsr.prototype.updateOptions = async function(options, update = true) {
ripe.Visual.prototype.updateOptions.call(this, options);
const updateScene = false;
let updateRenderedInitials = false;
let updateDebug = false;
// checks if it should trigger specific updates
updateRenderedInitials = updateRenderedInitials || updateScene;
updateDebug = updateDebug || updateScene;
updateDebug = updateDebug || updateRenderedInitials;
updateDebug = updateDebug || options.debug !== this.debug;
// update configurator variables
this.width = options.width === undefined ? this.width : options.width;
this.height = options.height === undefined ? this.height : options.height;
this.size = options.size === undefined ? this.size : options.size;
this.pixelRatio = options.pixelRatio === undefined ? this.pixelRatio : options.pixelRatio;
this.sensitivity = options.sensitivity === undefined ? this.sensitivity : options.sensitivity;
this.verticalThreshold =
options.verticalThreshold === undefined
? this.verticalThreshold
: options.verticalThreshold;
this.duration = options.duration === undefined ? this.duration : options.duration;
this.debug = options.debug === undefined ? this.debug : options.debug;
const debugOpts = options.debugOptions || {};
this.debugOptions = { ...this.debugOptions, ...debugOpts };
// update the configurator to use the newly applied values
if (update) {
await this.update(undefined, {
updateScene: updateScene,
updateRenderedInitials: updateRenderedInitials,
updateDebug: updateDebug
});
}
};
/**
* This function is called (by the owner) whenever its state changes
* so that the Configurator can update itself for the new state.
*
* This method is "protected" by unique signature validation in order
* to avoid extra render and frame loading operations. Operations are
* available to force the update operation even if the signature is the
* same as the one previously set.
*
* @param {Object} state An object containing the new state of the owner.
* @param {Object} options Set of optional parameters to adjust the Configurator update.
* @returns {Boolean} If an effective operation has been performed by the
* update operation.
*/
ripe.ConfiguratorCsr.prototype.update = async function(state, options = {}) {
this.loading = true;
const updateScene = Boolean(options.updateScene);
const updateRenderedInitials = Boolean(options.updateRenderedInitials);
const updateDebug = Boolean(options.updateDebug);
if (updateScene) {
this._deinitScene();
this._initScene();
}
if (updateRenderedInitials) {
this._deinitCsrRenderedInitials();
this._initCsrRenderedInitials();
}
if (updateDebug) {
this._deinitDebug();
this._initDebug();
}
this.loading = false;
this.trigger("ready");
return true;
};
/**
* This function is called (by the owner) whenever the current operation
* in the child should be canceled this way a Configurator is not updated.
*
* @param {Object} options Set of optional parameters to adjust the Configurator.
* @returns {Boolean} If an effective operation has been performed or if
* instead no cancel logic was executed.
*/
ripe.ConfiguratorCsr.prototype.cancel = async function(options = {}) {
return true;
};
/**
* Resizes the configurator's DOM element to 'size' pixels. You can also specify the
* width and height, the size applied is the more specific one.
*
* @param {Number} size The number of pixels to resize to.
*/
ripe.ConfiguratorCsr.prototype.resize = async function(size, width, height) {
if (!this.element) return;
const sizeValues = this._configuratorSize(size, width, height);
// in case the current size of the configurator ignores the
// request to avoid usage of unneeded resources
if (
this.size === sizeValues.size &&
this.width === sizeValues.width &&
this.height === sizeValues.height
) {
return;
}
this._resizeCsr(sizeValues.width, sizeValues.height);
this.size = sizeValues.size;
this.width = sizeValues.width;
this.height = sizeValues.height;
await this.update(
{},
{
force: true
}
);
};
/**
* Executes pending operations that were not performed so as
* to not conflict with the tasks already being executed.
*
* The main reason for collision is the loading operation
* being executed (long duration operation).
*
* @param {Boolean} tail If only the last pending operation should
* be flushed, meaning that the others are discarded.
*/
ripe.ConfiguratorCsr.prototype.flushPending = async function(tail = false) {
const pending =
tail && this._pendingOps.length > 0
? [this._pendingOps[this._pendingOps.length - 1]]
: this._pendingOps;
this._pendingOps = [];
while (pending.length > 0) {
const { operation, args } = pending.shift();
switch (operation) {
case "changeFrame":
await this.changeFrame(...args);
break;
default:
break;
}
}
};
/**
* Rotates the model to match the PRC frame.
*
* @param {Object} frame The new PRC frame to display using the extended and canonical
* format for the frame description (eg: side-3).
* @param {Object} options Set of optional parameters to adjust the change frame, such as:
* - 'duration' - The duration of the animation in milliseconds (defaults to 'null').
* - 'stepDuration' - If defined the total duration of the animation is
* calculated using the amount of steps times the number of steps, instead of
* using the 'duration' field (defaults to 'null').
* - 'revolutionDuration' - If defined the step duration is calculated by dividing
* the revolution duration by the number of frames in the view (defaults to 'null').
* - 'preventDrag' - If drag actions during an animated change of frames should be
* ignored (defaults to 'true').
* - 'safe' - If requested then the operation is only performed in case the configurator
* is not in the an equivalent state (default to 'true').
*/
ripe.ConfiguratorCsr.prototype.changeFrame = async function(frame, options = {}) {
// in case the scene group is not loaded then it's not possible to change the frame
if (!this.modelGroup) throw new Error("Model group not loaded");
// parses the requested frame value according to the pre-defined
// standard (eg: side-3) and then unpacks it as view and position
const _frame = ripe.parseFrameKey(frame);
const nextView = _frame[0];
const nextPosition = parseInt(_frame[1]);
// in case the next position value was not properly parsed (probably undefined)
// then it's not possible to change frame (throws exception)
if (isNaN(nextPosition)) {
throw new RangeError("Frame position is not defined");
}
// unpacks the other options to the frame change defaulting their values
// in case undefined values are found
let duration = options.duration !== undefined ? options.duration : null;
let stepDuration = options.stepDuration !== undefined ? options.stepDuration : null;
const revolutionDuration =
options.revolutionDuration !== undefined ? options.revolutionDuration : null;
const preventDrag = options.preventDrag !== undefined ? options.preventDrag : true;
const safe = options.safe !== undefined ? options.safe : true;
// retrieves the model frame object
const frames = await this.owner.getFrames();
// tries to retrieve the amount of frames for the target view and
// validates that the target view exists and that the target position
// (frame) does not overflow the amount of frames in for the view
const viewFramesNum = frames[nextView];
if (!viewFramesNum || nextPosition >= viewFramesNum) {
throw new RangeError("Frame " + frame + " is not supported");
}
// in case the safe mode is enabled and the current configuration is
// still under the loading situation the change frame operation is
// saved and will be executed after the loading is finished
if (safe && this.loading) {
this._pendingOps = [{ operation: "changeFrame", args: [frame, options] }];
return;
}
// normalizes the model group rotation values
ripe.CsrUtils.normalizeRotations(this.modelGroup);
// calculates step duration based on revolutionDuration defaulting to the stepDuration
// if no revolutionDuration is specified
stepDuration =
revolutionDuration !== null ? parseInt(revolutionDuration / viewFramesNum) : stepDuration;
// bases the duration on the stepDuration if stepDuration is specified
if (stepDuration) {
// default the step count to 1
let stepCount = 1;
// step count calculation logic for "side" view
if (nextView === "side") {
// calculates the PRC compatible step count for the change frame animation
const radPerSide = (2 * Math.PI) / viewFramesNum;
const rotYStart = ripe.CsrUtils.toPrecision(this.modelGroup.rotation.y);
const rotYEnd = ripe.CsrUtils.toPrecision(nextPosition * radPerSide);
const rotYQty = rotYEnd - rotYStart;
// gets the number of PRC compatible steps
stepCount = Math.abs(rotYQty / radPerSide);
}
// rounds up the step count to it's respective a whole number
const stepCountRounded = Math.ceil(stepCount);
// sets duration based on stepDuration
duration = stepDuration * stepCountRounded;
}
// ensures duration default if no duration is set
duration = duration !== null ? duration : this.duration;
// duration value compatible with CSR animation defaulting
// to 0 if no duration was successfully set
duration = duration ? duration / 1000 : 0;
// creates a change frame animation
const animation = new ripe.CsrChangeFrameAnimation(
this.modelGroup,
duration,
nextView,
nextPosition,
viewFramesNum
);
// sets the animation to clean the state when the animation finishes
animation.onFinished = () => {
this.isChangeFrameAnimationRunning = false;
this.noDrag = false;
};
// checks for change frame animations that are already running
const index = this.animations.findIndex(
animation => animation.type === "CsrChangeFrameAnimation"
);
const runningAnimation = this.animations[index];
if (runningAnimation) {
// ignore this call as the running animation is the same
if (runningAnimation.signature === animation.signature) {
this.isChangeFrameAnimationRunning = false;
this.noDrag = false;
return;
}
// removes the running animation as the new animation is different
this.animations.splice(index, 1);
}
// sets the needed state variables and adds the new animation so
// it can be run
this.isChangeFrameAnimationRunning = true;
this.noDrag = preventDrag;
this.animations.push(animation);
};
/**
* Syncs the CSR configurator state to a PRC configurator state.
*
* @param {ConfiguratorPrc} prcConfigurator The PRC configurator.
*/
ripe.ConfiguratorCsr.prototype.syncFromPRC = async function(prcConfigurator) {
// sets the CSR configurator state
const size = prcConfigurator.element.dataset.size || prcConfigurator.size;
const width = prcConfigurator.element.dataset.width || prcConfigurator.width || size;
const height = prcConfigurator.element.dataset.height || prcConfigurator.height || size;
await this.updateOptions({
width: parseInt(width),
height: parseInt(height),
size: parseInt(size),
pixelRatio: prcConfigurator.pixelRatio,
sensitivity: prcConfigurator.sensitivity,
verticalThreshold: prcConfigurator.verticalThreshold,
duration: prcConfigurator.duration
});
// resizes the CSR configurator to match the PRC size
await this.resize();
// sets the CSR configurator visuals so it matches the PRC frame
const frame = ripe.getFrameKey(prcConfigurator.view, prcConfigurator.position);
await this.changeFrame(frame, { duration: 0 });
// resets camera zoom to starting value
this._setZoom(1);
};
ripe.ConfiguratorCsr.prototype.prcFrame = async function() {
if (!this.modelGroup) return null;
// gets PRC frames object
const frames = await this.owner.getFrames();
// normalizes the model group rotations
ripe.CsrUtils.normalizeRotations(this.modelGroup);
// converts the model group x axis rotation value to degrees
const verticalDeg = THREE.MathUtils.radToDeg(this.modelGroup.rotation.x);
// checks if CSR state is equivalent to PRC top frame
const topDegMin = 90 - this.verticalThreshold;
const topDegMax = 90 + this.verticalThreshold;
const isTop = verticalDeg >= topDegMin && verticalDeg <= topDegMax;
if (isTop && frames.top !== undefined) {
return "top-0";
}
// checks if CSR state is equivalent to PRC bottom frame
const bottomDegMin = 270 - this.verticalThreshold;
const bottomDegMax = 270 + this.verticalThreshold;
const isBottom = verticalDeg >= bottomDegMin && verticalDeg <= bottomDegMax;
if (isBottom && frames.bottom !== undefined) {
return "bottom-0";
}
// calculates the PRC equivalent side frame
const framesNum = frames.side || 0;
const radPerSide = (Math.PI * 2) / framesNum;
const position = ripe.CsrUtils.toPrecision(this.modelGroup.rotation.y / radPerSide, 4);
const positionRounded = Math.round(position);
return `side-${positionRounded}`;
};
/**
* Sets the callback function for the post render call.
*
* @param {Function} callback The function to be called.
*/
ripe.ConfiguratorCsr.prototype.setPostRender = function(callback) {
this._postRenderCallback = callback;
};
/**
* Clears the post render callback registry.
*/
ripe.ConfiguratorCsr.prototype.unsetPostRender = function() {
this._postRenderCallback = null;
};
/**
* Tries to obtain the best possible size for the configurator
* defaulting to the client with of the element as fallback.
*
* @param {Number} size The number of pixels for the viewport both width and height.
* @param {Number} width The number of pixels for the viewport width.
* @param {Number} height The number of pixels for the viewport height.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._configuratorSize = function(size, width, height) {
size = size || this.element.dataset.size || this.size || this.element.clientWidth;
width = width || this.element.dataset.width || this.width || size;
height = height || this.element.dataset.height || this.height || size;
return {
size: parseInt(size),
width: parseInt(width),
height: parseInt(height)
};
};
/**
* Loads a font from a URL and adds it to the document font set.
*
* @param {Number} name The name to give to the font.
* @param {Number} url The URL for the font file.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._loadExternalFont = async function(name, url) {
if (!name) throw new Error("Name is required");
if (!url) throw new Error("URL is required");
const font = new FontFace(name, `url(${url})`);
await font.load();
document.fonts.add(font);
};
/**
* Loads a mesh.
*
* @param {String} path Path to the file. Can be local path or an URL.
* @param {String} format Mesh file format.
* @returns {THREE.Mesh} The loaded model.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._loadMesh = async function(path, format = "gltf") {
switch (format) {
case "gltf":
case "glb":
return await ripe.CsrUtils.loadGLTF(path, this.dracoLoader);
case "fbx":
return await ripe.CsrUtils.loadFBX(path);
default:
throw new Error(`Can't load 3D model, format "${format}" is not supported`);
}
};
/**
* Loads a Maya exported fbx scene.
*
* @param {String} path Path to the file. Can be local path or an URL.
* @param {String} format File format for the scene file.
* @returns {Object} Information about the scene.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._loadMayaScene = async function(path, format = "fbx") {
const scene = {
camera: {},
cameraLookAt: {}
};
switch (format) {
case "fbx": {
const fbxObj = await ripe.CsrUtils.loadFBX(path);
// gets information about the side camera
const sideCamera = fbxObj.getObjectByName("sideCam");
if (sideCamera) {
scene.camera = {
fov: sideCamera.fov,
filmGauge: sideCamera.filmGauge,
position: {
x: sideCamera.position.x,
y: sideCamera.position.y,
z: sideCamera.position.z
}
};
}
// gets information about the side camera aim
const sideCameraAim = fbxObj.getObjectByName("sideCam_aim");
if (sideCameraAim) {
scene.cameraLookAt = {
x: sideCameraAim.position.x,
y: sideCameraAim.position.y,
z: sideCameraAim.position.z
};
}
break;
}
case "json":
{
const response = await fetch(path);
const data = await response.json();
scene.camera = data.camera;
scene.cameraLookAt = data.cameraLookAt;
}
break;
default:
throw new Error(`Can't load Maya scene, format "${format}" is not supported`);
}
return scene;
};
/**
* Initializes the layout for the configurator element by
* constructing all te child elements required for the proper
* configurator functionality to work.
*
* From a DOM perspective this is a synchronous operation,
* meaning that after its execution the configurator is ready
* to be manipulated.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initLayout = function() {
// in case the element is no longer available (possible due to async
// nature of execution) returns the control flow immediately
if (!this.element) return;
// clears the elements children by iterating over them
while (this.element.firstChild) {
this.element.removeChild(this.element.firstChild);
}
// creates the renderer canvas and adds it to the element
const renderer = ripe.createElement("div", "renderer");
this.element.appendChild(renderer);
// creates the initials container and its canvas
const initialsContainer = ripe.createElement("div", "initials-container");
const initialsCanvas = ripe.createElement("canvas", "canvas");
const initialsDisplacementCanvas = ripe.createElement("canvas", "displacement");
initialsContainer.appendChild(initialsCanvas);
initialsContainer.appendChild(initialsDisplacementCanvas);
// hides the initials container
initialsContainer.style.display = "none";
// adds the initials container to the element
this.element.appendChild(initialsContainer);
// register for all the necessary DOM events
this._registerElementHandlers();
};
/**
* Creates and initiates the renderer scene camera.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initCamera = function() {
this.camera = new THREE.PerspectiveCamera(
this.cameraOptions.fov,
this.cameraOptions.aspect,
this.cameraOptions.near,
this.cameraOptions.far
);
if (this.cameraOptions.filmGauge) this.camera.filmGauge = this.cameraOptions.filmGauge;
ripe.CsrUtils.applyTransform(
this.camera,
this.cameraOptions.position,
this.cameraOptions.rotation,
this.cameraOptions.scale
);
if (this.cameraOptions.lookAt) {
this.camera.lookAt(
this.cameraOptions.lookAt.x,
this.cameraOptions.lookAt.y,
this.cameraOptions.lookAt.z
);
}
};
/**
* Loads the build scene by setting it's environment and adding it's model to
* the renderer scene.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initScene = function() {
// updates renderer options
this.renderer.toneMapping = this.rendererOptions.toneMapping;
this.renderer.toneMappingExposure = this.rendererOptions.toneMappingExposure;
// creates empty scene
this.scene = new THREE.Scene();
// gets configurator size information
const size = this._configuratorSize();
// calculates the camera aspect ratio
if (this.cameraOptions.aspect === null) {
this.cameraOptions.aspect = size.width / size.height;
}
// inits camera thats going to be used to view the scene
this._initCamera();
// sets the scene environment
if (this.environmentTexture) {
this.environmentTexture.mapping = THREE.EquirectangularReflectionMapping;
this.scene.environment = this.environmentTexture;
}
// inits the scene model group
this.modelGroup = new THREE.Group();
// sets the model mesh
this.modelGroup.add(this.mesh);
// adds model group to the scene
this.scene.add(this.modelGroup);
};
/**
* Cleanups scene.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._deinitScene = function() {
if (this.modelGroup) this.modelGroup = null;
if (this.scene) this.scene = null;
if (this.camera) this.camera = null;
};
/**
* Free resources used by the scene.
*/
ripe.ConfiguratorCsr.prototype._unloadSceneResources = function() {
if (this.environmentTexture) {
this.environmentTexture.dispose();
this.environmentTexture = null;
}
if (this.mesh) {
if (this.mesh.geometry) this.mesh.geometry.dispose();
if (this.mesh.material) this.mesh.material.dispose();
if (this.modelGroup) this.modelGroup.remove(this.mesh);
this.mesh = null;
}
};
/**
* Completely cleanup and destroy the scene.
*/
ripe.ConfiguratorCsr.prototype._destroyScene = function() {
this._unloadSceneResources();
this._deinitScene();
};
/**
* Initializes the CSR initials. This means initializing an instance of `CsrRenderedInitials`,
* and doing it's setup.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initCsrRenderedInitials = function() {
if (!this.enabledInitials) return;
this._unregisterInitialsGlobalHandlers();
this._registerInitialsGlobalHandlers();
const initialsContainer = this.element.querySelector(".initials-container");
if (!initialsContainer) {
throw new Error("Initials container not initialized, can't initiate CSR initials");
}
const canvas = initialsContainer.querySelector(".canvas");
const displacementCanvas = initialsContainer.querySelector(".displacement");
this.initialsRefs.renderedInitials = new ripe.CsrRenderedInitials(
canvas,
displacementCanvas,
this.initialsOptions.width,
this.initialsOptions.height,
this.pixelRatio,
this.initialsOptions.options
);
// apply textures to initials
if (this.initialsRefs.baseTexture) {
this.initialsRefs.renderedInitials.setTexture(
"base",
this.initialsRefs.baseTexture,
this.initialsOptions.options.baseTextureOptions
);
}
if (this.initialsRefs.displacementTexture) {
this.initialsRefs.renderedInitials.setTexture(
"displacement",
this.initialsRefs.displacementTexture,
this.initialsOptions.options.displacementTextureOptions
);
}
if (this.initialsRefs.metallicTexture) {
this.initialsRefs.renderedInitials.setTexture(
"metallic",
this.initialsRefs.metallicTexture,
this.initialsOptions.options.metallicTextureOptions
);
}
if (this.initialsRefs.normalTexture) {
this.initialsRefs.renderedInitials.setTexture(
"normal",
this.initialsRefs.normalTexture,
this.initialsOptions.options.normalTextureOptions
);
}
if (this.initialsRefs.roughnessTexture) {
this.initialsRefs.renderedInitials.setTexture(
"roughness",
this.initialsRefs.roughnessTexture,
this.initialsOptions.options.roughnessTextureOptions
);
}
// uses rendered initials mesh
this.initialsRefs.mesh = this.initialsRefs.renderedInitials.getMesh();
this.modelGroup.add(this.initialsRefs.mesh);
// applies the mesh reference points if available
if (this.initialsOptions.points && this.initialsOptions.points.length > 0) {
const vec3Points = this.initialsOptions.points.map(point =>
ripe.CsrUtils.toVector3({
x: point.x / this.INITIALS_SCALE_MULTIPLIER,
y: point.y / this.INITIALS_SCALE_MULTIPLIER,
z: point.z / this.INITIALS_SCALE_MULTIPLIER
})
);
this.initialsRefs.renderedInitials.setPoints(vec3Points);
}
// applies the mesh transformations
const scale = this.initialsOptions.scale * this.INITIALS_SCALE_MULTIPLIER;
ripe.CsrUtils.applyTransform(
this.initialsRefs.mesh,
this.initialsOptions.position,
this.initialsOptions.rotation,
{ x: scale, y: scale, z: scale }
);
// trigger rerender to clear the initials
this.initialsRefs.renderedInitials.rerenderInitials();
};
/**
* Cleanups CSR initials.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._deinitCsrRenderedInitials = function() {
// cleanup handlers
this._unregisterInitialsGlobalHandlers();
};
/**
* Free resources used by the csr initials.
*/
ripe.ConfiguratorCsr.prototype._unloadCsrRenderedInitialsResources = function() {
// cleanup loaded textures
if (this.initialsRefs.baseTexture) this.initialsRefs.baseTexture.dispose();
if (this.initialsRefs.displacementTexture) this.initialsRefs.displacementTexture.dispose();
if (this.initialsRefs.metallicTexture) this.initialsRefs.metallicTexture.dispose();
if (this.initialsRefs.normalTexture) this.initialsRefs.normalTexture.dispose();
if (this.initialsRefs.roughnessTexture) this.initialsRefs.roughnessTexture.dispose();
// free all resources used
if (this.initialsRefs.renderedInitials) this.initialsRefs.renderedInitials.destroy();
this.initialsRefs = {
renderedInitials: null,
mesh: null,
baseTexture: null,
displacementTexture: null,
metallicTexture: null,
normalTexture: null,
roughnessTexture: null
};
};
/**
* Completely cleanup and destroy CSR initials.
*/
ripe.ConfiguratorCsr.prototype._destroyInitialsResources = function() {
this._unloadCsrRenderedInitialsResources();
this._deinitCsrRenderedInitials();
};
/**
* Initiates the debug tools.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initDebug = function() {
if (!this.debug) return;
// inits framerate panel
if (this.debugOptions.framerate) {
const renderer = this.element.querySelector(".renderer");
if (!renderer) {
throw new Error("Renderer container not initialized, can't load debug framerate");
}
this.debugRefs.framerate = new window.Stats();
this.debugRefs.framerate.dom.classList.add("framerate-panel");
renderer.appendChild(this.debugRefs.framerate.dom);
}
// inits world axis
if (this.debugOptions.worldAxis) {
if (!this.scene) throw new Error("Scene not initialized, can't load debug axis");
this.debugRefs.worldAxis = new THREE.AxesHelper(100);
this.scene.add(this.debugRefs.worldAxis);
}
// inits model group axis
if (this.debugOptions.modelAxis) {
if (!this.modelGroup) throw new Error("Model group not initialized, can't load debug axis");
this.debugRefs.modelAxis = new THREE.AxesHelper(50);
this.modelGroup.add(this.debugRefs.modelAxis);
}
// inits rendered initials debug tools
if (this.enabledInitials && this.debugOptions.renderedInitials) {
if (!this.modelGroup) {
throw new Error("Model group not initialized, can't load rendered initials debug tool");
}
if (!this.initialsRefs.renderedInitials) {
throw new Error(
"CSR initials not initialized, can't load rendered initials debug tool"
);
}
// creates group that will contain all rendered initials debug tools
this.debugRefs.renderedInitials.group = new THREE.Group();
// inits axis
if (this.debugOptions.renderedInitials.axis) {
this.debugRefs.renderedInitials.axis = new THREE.AxesHelper(750);
this.debugRefs.renderedInitials.group.add(this.debugRefs.renderedInitials.axis);
}
// ensures it has the minimum number of points
if (this.initialsRefs.renderedInitials.points.length > 1) {
// inits reference points curve
if (this.debugOptions.renderedInitials.line) {
const curve = new THREE.CatmullRomCurve3(
this.initialsRefs.renderedInitials.points,
false,
this.initialsRefs.renderedInitials.curveOptions.type,
this.initialsRefs.renderedInitials.curveOptions.tension
);
const pointsNum = 50;
const linePoints = curve.getPoints(pointsNum);
this.debugRefs.renderedInitials.line = new THREE.Line(
new THREE.BufferGeometry().setFromPoints(linePoints),
new THREE.LineBasicMaterial({ color: 0x0000ff })
);
this.debugRefs.renderedInitials.group.add(this.debugRefs.renderedInitials.line);
}
// inits reference points
if (this.debugOptions.renderedInitials.points) {
const boxGeometry = new THREE.BoxGeometry(30, 30, 30);
const boxMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
for (const pos of this.initialsRefs.renderedInitials.points) {
const pointBox = new THREE.Mesh(boxGeometry, boxMaterial);
pointBox.position.copy(pos);
this.debugRefs.renderedInitials.points.push(pointBox);
this.debugRefs.renderedInitials.group.add(pointBox);
}
}
}
// adjust object transforms
const scale = this.initialsOptions.scale * this.INITIALS_SCALE_MULTIPLIER;
ripe.CsrUtils.applyTransform(
this.debugRefs.renderedInitials.group,
this.initialsOptions.position,
this.initialsOptions.rotation,
{ x: scale, y: scale, z: scale }
);
this.modelGroup.add(this.debugRefs.renderedInitials.group);
}
};
/**
* Cleanups everything related to the debug tools.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._deinitDebug = function() {
if (this.debugRefs.renderedInitials) {
// cleanup reference points boxes
this.debugRefs.renderedInitials.points.forEach(point => {
if (point.geometry) point.geometry.dispose();
if (point.material) point.material.dispose();
if (this.debugRefs.renderedInitials.group) {
this.debugRefs.renderedInitials.group.remove(point);
}
});
this.debugRefs.renderedInitials.points = [];
// cleanup line
if (this.debugRefs.renderedInitials.line) {
if (this.debugRefs.renderedInitials.line.geometry) {
this.debugRefs.renderedInitials.line.geometry.dispose();
}
if (this.debugRefs.renderedInitials.line.material) {
this.debugRefs.renderedInitials.line.material.dispose();
}
if (this.debugRefs.renderedInitials.group) {
this.debugRefs.renderedInitials.group.remove(this.debugRefs.renderedInitials.line);
}
this.debugRefs.renderedInitials.line = null;
}
// cleanup axis
if (this.debugRefs.renderedInitials.axis) {
if (this.debugRefs.renderedInitials.group) {
this.debugRefs.renderedInitials.group.remove(this.debugRefs.renderedInitials.line);
}
this.debugRefs.renderedInitials.axis = null;
}
// cleanup group
this.debugRefs.renderedInitials.group = null;
}
if (this.debugRefs.modelAxis) {
this.debugRefs.modelAxis.dispose();
this.modelGroup.remove(this.debugRefs.modelAxis);
this.debugRefs.modelAxis = null;
}
if (this.debugRefs.worldAxis) {
this.debugRefs.worldAxis.dispose();
this.scene.remove(this.debugRefs.worldAxis);
this.debugRefs.worldAxis = null;
}
if (this.debugRefs.framerate) {
this.debugRefs.framerate.dom.remove();
this.debugRefs.framerate = null;
}
};
/**
* Free debug tools resources.
*/
ripe.ConfiguratorCsr.prototype._unloadDebugResources = function() {};
/**
* Completely cleanup and destroy debug tools.
*/
ripe.ConfiguratorCsr.prototype._destroyDebug = function() {
this._unloadDebugResources();
this._deinitDebug();
};
/**
* Initializes and loads everything needed to run the CSR. This means
* initializing the renderer, it's camera and it's scene.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._initCsr = function() {
if (!this.element) throw new Error("CSR layout elements are not initiated");
// gets configurator size information
const size = this._configuratorSize();
// init renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.setPixelRatio(this.pixelRatio);
this.renderer.setSize(size.width, size.height);
this.renderer.setAnimationLoop(() => this._onAnimationLoop(this));
// applies tone mapping
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
this.renderer.toneMappingExposure = 1;
const renderer = this.element.querySelector(".renderer");
renderer.appendChild(this.renderer.domElement);
// internal clock initialization
this.clock = new THREE.Clock();
};
/**
* Cleanups everything related to CSR.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._deinitCsr = function() {
this._destroyDebug();
this._destroyInitialsResources();
this._destroyScene();
if (this.renderer) {
this.renderer.dispose();
this.renderer = null;
}
};
/**
* Unpacks a set of texture options originated from the config so it can be used by the
* CSR configurator.
*
* @param {Object} options Set of texture options originated from the config.
* @returns {Object} The unpacked texture set of options.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._unpackTextureOptions = function(options) {
const unpacked = {};
if (options.name) unpacked.name = options.name;
if (options.wrap_s) unpacked.wrapS = ripe.CsrUtils.toWrappingModeValue(options.wrap_s);
if (options.wrap_t) unpacked.wrapT = ripe.CsrUtils.toWrappingModeValue(options.wrap_t);
if (options.offset) unpacked.offset = ripe.CsrUtils.toXYZObject(options.offset);
if (options.repeat) unpacked.repeat = ripe.CsrUtils.toXYZObject(options.repeat);
if (options.rotation) unpacked.rotation = THREE.MathUtils.degToRad(options.rotation);
if (options.center) unpacked.center = ripe.CsrUtils.toXYZObject(options.center);
return unpacked;
};
/**
* Unpacks a set of initials options originated from the config so it can be used by the
* CSR configurator.
*
* @param {Object} options Set of initials options originated from the config.
* @returns {Object} The unpacked initials set of options.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._unpackInitialsOptions = function(initialsConfig) {
const unpacked = {};
// gets CSR specific options
const csrOptions = initialsConfig.csr || {};
// unpacks root options
unpacked.width = csrOptions.width;
unpacked.height = csrOptions.height;
const points = csrOptions.points || [];
unpacked.points = points.map(p => ripe.CsrUtils.toXYZObject(p));
unpacked.position = csrOptions.position
? ripe.CsrUtils.toXYZObject(csrOptions.position)
: undefined;
unpacked.rotation = csrOptions.rotation
? ripe.CsrUtils.toXYZObject(csrOptions.rotation)
: undefined;
unpacked.scale = csrOptions.scale;
// unpacks curve options
const curveOptions = {};
curveOptions.type = csrOptions.curve_type;
curveOptions.tension = csrOptions.curve_tension;
// unpacks text options
const textOptions = {};
if (csrOptions.text) {
textOptions.fontSize = csrOptions.text.font_size;
textOptions.font = csrOptions.text.font_weight
? `${csrOptions.text.font_family}-${csrOptions.text.font_weight}`
: csrOptions.text.font_family;
textOptions.fontFamily = csrOptions.text.font_family;
textOptions.fontWeight = csrOptions.text.font_weight;
textOptions.fontFormat = csrOptions.text.font_format;
textOptions.xOffset = csrOptions.text.x_offset;
textOptions.yOffset = csrOptions.text.y_offset;
textOptions.lineWidth = csrOptions.text.stroke_width;
textOptions.displacementMapTextBlur = csrOptions.text.displacement_blur;
textOptions.normalMapBlurIntensity = csrOptions.text.normal_map_blur;
}
// unpacks material options
const materialOptions = {};
if (csrOptions.material) {
const materialOpts = csrOptions.material;
materialOptions.color = materialOpts.color ? `#${materialOpts.color}` : undefined;
materialOptions.metalness = materialOpts.metalness;
materialOptions.roughness = materialOpts.roughness;
materialOptions.emissive = materialOpts.emissive_color
? `#${materialOpts.emissive_color}`
: undefined;
materialOptions.emissiveIntensity = materialOpts.emissive_intensity;
materialOptions.displacementScale = materialOpts.displacement_scale;
materialOptions.displacementBias = materialOpts.displacement_bias;
}
// unpacks mesh options
const meshOptions = {};
if (csrOptions.mesh) {
const meshOpts = csrOptions.mesh;
meshOptions.widthSegments = meshOpts.width_segments;
meshOptions.heightSegments = meshOpts.height_segments;
}
// unpacks textures options
const baseTextureOptions = csrOptions.base_texture
? this._unpackTextureOptions(csrOptions.base_texture)
: {};
const displacementTextureOptions = csrOptions.displacement_texture
? this._unpackTextureOptions(csrOptions.displacement_texture)
: {};
const metallicTextureOptions = csrOptions.metallic_texture
? this._unpackTextureOptions(csrOptions.metallic_texture)
: {};
const normalTextureOptions = csrOptions.normal_texture
? this._unpackTextureOptions(csrOptions.normal_texture)
: {};
const roughnessTextureOptions = csrOptions.roughness_texture
? this._unpackTextureOptions(csrOptions.roughness_texture)
: {};
// builds options params
unpacked.options = {
curveOptions: curveOptions,
textOptions: textOptions,
materialOptions: materialOptions,
meshOptions: meshOptions,
baseTextureOptions: baseTextureOptions,
displacementTextureOptions: displacementTextureOptions,
metallicTextureOptions: metallicTextureOptions,
normalTextureOptions: normalTextureOptions,
roughnessTextureOptions: roughnessTextureOptions
};
return unpacked;
};
/**
* Unpacks a set of scene options originated from the config so it can be used by the
* CSR configurator.
*
* @param {Object} options Set of scene options originated from the config.
* @returns {Object} The unpacked scene set of options.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._unpackSceneOptions = function(options) {
const rendererOptions = {};
const cameraOptions = {};
const zoomOptions = {};
// unpacks root options
const environment = options.environment;
// unpacks renderer options
rendererOptions.toneMapping = options.tone_mapping
? ripe.CsrUtils.toToneMappingValue(options.tone_mapping)
: undefined;
rendererOptions.toneMappingExposure = options.tone_mapping_exposure;
// unpacks scene zoom options
if (options.zoom) {
zoomOptions.enabled = options.zoom.enabled;
zoomOptions.min = options.zoom.min;
zoomOptions.max = options.zoom.max;
zoomOptions.sensitivity = options.zoom.sensitivity;
}
// unpacks scene camera options
if (options.camera) {
cameraOptions.position = options.camera.position
? ripe.CsrUtils.toXYZObject(options.camera.position)
: undefined;
cameraOptions.rotation = options.camera.rotation
? ripe.CsrUtils.toXYZObject(options.camera.rotation)
: undefined;
cameraOptions.fov = options.camera.fov;
cameraOptions.filmGauge = options.camera.film_gauge;
cameraOptions.aspect = options.camera.aspect;
cameraOptions.near = options.camera.near;
cameraOptions.far = options.camera.far;
}
if (options.camera_look_at) {
cameraOptions.lookAt = ripe.CsrUtils.toXYZObject(options.camera_look_at);
}
return {
environment: environment,
rendererOptions: rendererOptions,
cameraOptions: cameraOptions,
zoomOptions: zoomOptions
};
};
/**
* Loads all the assets needed for the CSR to work.
*
* @param {Object} options Set of scene options.
* @param {Object} options Set of initials options.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._loadCsrAssets = async function(
config,
sceneOptions,
initialsOptions
) {
const variant = this.owner.variant || "$base";
// loads mesh info
const meshesConfig = config.meshes || {};
const meshInfo = meshesConfig[variant] || {};
const meshUrl = this.owner.getMeshUrl({ variant: variant });
const meshFormat = meshInfo.format || "glb";
// loads needed loaders for the meshes
this.dracoLoader = ripe.CsrUtils.loadDracoLoader();
// computes environment file URL
const environment = sceneOptions.environment || "studio2";
const environmentFormat = "hdr";
const environmentUrl = this.owner.getSceneEnvironmentUrl(environment, environmentFormat);
const optionsParams = initialsOptions.options || {};
// computes initials font URL
const font = optionsParams.textOptions ? optionsParams.textOptions.font : null;
const fontFamily = optionsParams.textOptions ? optionsParams.textOptions.fontFamily : null;
const fontWeight = optionsParams.textOptions ? optionsParams.textOptions.fontWeight : null;
const fontFormat = optionsParams.textOptions ? optionsParams.textOptions.fontFormat : "ttf";
const fontUrl = fontFamily
? this.owner.getFontUrl(fontFamily, fontFormat, { weight: fontWeight })
: null;
// computes initials textures URLs
const baseTextureName = optionsParams.baseTextureOptions
? optionsParams.baseTextureOptions.name
: null;
const baseTextureUrl = baseTextureName
? this.owner.getTextureMapUrl("pattern", baseTextureName)
: null;
const displacementTextureName = optionsParams.displacementTextureOptions
? optionsParams.displacementTextureOptions.name
: null;
const displacementTextureUrl = displacementTextureName
? this.owner.getTextureMapUrl("displacement", displacementTextureName)
: null;
const metallicTextureName = optionsParams.metallicTextureOptions
? optionsParams.metallicTextureOptions.name
: null;
const metallicTextureUrl = metallicTextureName
? this.owner.getTextureMapUrl("metallic", metallicTextureName)
: null;
const normalTextureName = optionsParams.normalTextureOptions
? optionsParams.normalTextureOptions.name
: null;
const normalTextureUrl = normalTextureName
? this.owner.getTextureMapUrl("normal", normalTextureName)
: null;
const roughnessTextureName = optionsParams.roughnessTextureOptions
? optionsParams.roughnessTextureOptions.name
: null;
const roughnessTextureUrl = roughnessTextureName
? this.owner.getTextureMapUrl("roughness", roughnessTextureName)
: null;
// loads assets
[
this.mesh,
,
this.environmentTexture,
this.initialsRefs.baseTexture,
this.initialsRefs.displacementTexture,
this.initialsRefs.metallicTexture,
this.initialsRefs.normalTexture,
this.initialsRefs.roughnessTexture
] = await Promise.all([
this._loadMesh(meshUrl, meshFormat),
fontUrl ? this._loadExternalFont(font, fontUrl) : null,
environmentUrl ? ripe.CsrUtils.loadEnvironment(environmentUrl, environmentFormat) : null,
baseTextureUrl ? ripe.CsrUtils.loadTexture(baseTextureUrl) : null,
displacementTextureUrl ? ripe.CsrUtils.loadTexture(displacementTextureUrl) : null,
metallicTextureUrl ? ripe.CsrUtils.loadTexture(metallicTextureUrl) : null,
normalTextureUrl ? ripe.CsrUtils.loadTexture(normalTextureUrl) : null,
roughnessTextureUrl ? ripe.CsrUtils.loadTexture(roughnessTextureUrl) : null
]);
};
/**
* Initializes this CSR configurator instance configuration by applying the default
* config values.
*
* @param {Object} options Set of options to override the defaults.
*/
ripe.ConfiguratorCsr.prototype._initConfigDefaults = function(options) {
const rendererOpts = options.rendererOptions || {};
this.rendererOptions = {
outputEncoding:
rendererOpts.outputEncoding !== undefined
? rendererOpts.outputEncoding
: THREE.sRGBEncoding,
toneMapping:
rendererOpts.toneMapping !== undefined
? rendererOpts.toneMapping
: THREE.ACESFilmicToneMapping,
toneMappingExposure:
rendererOpts.toneMappingExposure !== undefined ? rendererOpts.toneMappingExposure : 1
};
const cameraOpts = options.cameraOptions || {};
this.cameraOptions = {
fov: cameraOpts.fov !== undefined ? cameraOpts.fov : 24.678,
filmGauge: cameraOpts.filmGauge !== undefined ? cameraOpts.filmGauge : null,
aspect: cameraOpts.aspect !== undefined ? cameraOpts.aspect : null,
updateAspectOnResize:
cameraOpts.updateAspectOnResize !== undefined ? cameraOpts.updateAspectOnResize : true,
near: cameraOpts.near !== undefined ? cameraOpts.near : 0.1,
far: cameraOpts.far !== undefined ? cameraOpts.far : 10000,
position: cameraOpts.position !== undefined ? cameraOpts.position : { x: 0, y: 0, z: 207 },
rotation: cameraOpts.rotation !== undefined ? cameraOpts.rotation : { x: 0, y: 0, z: 0 },
scale: cameraOpts.scale !== undefined ? cameraOpts.scale : { x: 1, y: 1, z: 1 },
lookAt: cameraOpts.lookAt !== undefined ? cameraOpts.lookAt : null
};
const zoomOpts = options.zoomOptions || {};
this.zoomOptions = {
enabled: zoomOpts.enabled !== undefined ? zoomOpts.enabled : true,
sensitivity: zoomOpts.sensitivity !== undefined ? zoomOpts.sensitivity : 1,
min: zoomOpts.min !== undefined ? zoomOpts.min : 0.75,
max: zoomOpts.max !== undefined ? zoomOpts.max : 1.5
};
this.enabledInitials = options.enabledInitials || false;
const initialsOpts = options.initialsOptions || {};
this.initialsOptions = {
width: initialsOpts.width !== undefined ? initialsOpts.width : 3000,
height: initialsOpts.height !== undefined ? initialsOpts.height : 300,
options: initialsOpts.options !== undefined ? initialsOpts.options : {},
points: initialsOpts.points !== undefined ? initialsOpts.points : [],
position:
initialsOpts.position !== undefined ? initialsOpts.position : { x: 0, y: 0, z: 0 },
rotation:
initialsOpts.rotation !== undefined ? initialsOpts.rotation : { x: 0, y: 0, z: 0 },
scale: initialsOpts.scale !== undefined ? initialsOpts.scale : 1
};
};
/**
* Renders frame.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._render = function() {
if (!this.scene) throw new Error("Scene not initiated");
if (!this.camera) throw new Error("Camera not initiated");
if (!this.renderer) throw new Error("Renderer not initiated");
this.renderer.render(this.scene, this.camera);
this._onPostRender();
};
/**
* Sets the camera zoom, will trigger the update of the
* projection matrix in conformance.
*
* @param {Number} zoom The zoom number value.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._setZoom = function(zoom) {
if (!this.camera) throw new Error("Camera not initialized");
this.camera.zoom = zoom;
this.camera.updateProjectionMatrix();
};
/**
* Do the resize operation for every CSR element.
*
* @param {Number} width The number of pixels to resize to.
* @param {Number} height The number of pixels to resize to.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._resizeCsr = function(width, height) {
// resizes renderer
this.renderer.setSize(width, height);
// updates the camera aspect ratio
if (this.cameraOptions.updateAspectOnResize || this.cameraOptions.aspect === null) {
this.cameraOptions.aspect = width / height;
}
// creates a new camera respecting the new renderer size
this._initCamera();
};
/**
* Animation loop tick.
*
* @private
*/
ripe.ConfiguratorCsr.prototype._onAnimationLoop = function(self) {
if (self.loading) return;
// processes debug related ticks
if (self.debug) {
if (self.debugRefs.framerate) self.debugRefs.framerate.update();
}
// processes the animation loop tick delta
const delta = self.clock.getDelta();
if (!self.modelGroup) return;
// checks if there are animations to process
if (self.animations.length > 0) {
// ticks animations
for (let i = self.animations.length - 1; i >= 0; i--) {
const animation = self.animations[i];
animation.tick(delta);
if (animation.isFinished()) self.animations.splice(i, 1);
}
// normalizes the model group rotations
ripe.CsrUtils.normalizeRotations(self.modelGroup);
}
// renders a frame
self._render();
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onPostRender = function() {
if (this._postRenderCallback) this._postRenderCallback();
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onMouseDown = function(self, event) {
self.isMouseDown = true;
self.referenceX = event.pageX;
self.referenceY = event.pageY;
self.prevPercentX = 0;
self.prevPercentY = 0;
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onMouseUp = function(self, event) {
self.isMouseDown = false;
self.prevPercentX = 0;
self.prevPercentY = 0;
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onMouseLeave = function(self, event) {
self.isMouseDown = false;
self.prevPercentX = 0;
self.prevPercentY = 0;
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onMouseMove = function(self, event) {
if (!self.isMouseDown) return;
if (!self.modelGroup) return;
if (self.noDrag) return;
const mousePosX = event.pageX;
const mousePosY = event.pageY;
const deltaX = self.referenceX - mousePosX;
const deltaY = self.referenceY - mousePosY;
const elementWidth = self.element.clientWidth;
const elementHeight = self.element.clientHeight;
const percentX = deltaX / elementWidth;
const percentY = deltaY / elementHeight;
const sensitivity = self.sensitivity * 0.1;
const dragValueX = (percentX - self.prevPercentX) * sensitivity;
self.modelGroup.rotation.y -= dragValueX;
const dragValueY = (percentY - self.prevPercentY) * sensitivity;
self.modelGroup.rotation.x -= dragValueY;
self.prevPercentX = percentX;
self.prevPercentY = percentY;
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onWheel = function(self, event) {
event.preventDefault();
if (!self.zoomOptions.enabled) return;
if (!self.modelGroup) return;
if (!self.camera) return;
// calculates zoom value
let zoom = self.camera.zoom + event.deltaY * -(self.zoomOptions.sensitivity / 1000);
zoom = Math.min(Math.max(self.zoomOptions.min, zoom), self.zoomOptions.max);
// updates camera zoom, this will trigger
// the update of the projection matrix
self._setZoom(zoom);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onInitials = function(self, initials, engraving, params = {}) {
if (!this.initialsRefs.renderedInitials) throw new Error("CSR initials not initialized");
this.initialsRefs.renderedInitials.setInitials(initials);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onInitialsExtra = function(self, initialsExtra, params = {}) {
if (!this.initialsRefs.renderedInitials) throw new Error("CSR initials not initialized");
const hasEmptyInitials = Object.values(initialsExtra).length === 0;
const initialsGroups = Object.values(initialsExtra);
const isSingleGroup = initialsGroups.length === 1;
if (!hasEmptyInitials && !isSingleGroup) throw new Error("Not implemented");
this.initialsRefs.renderedInitials.setInitials(
hasEmptyInitials ? "" : initialsGroups[0].initials || ""
);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onPreConfig = function(self) {
self.loading = true;
this._destroyDebug();
this._destroyInitialsResources();
this._destroyScene();
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._onPostConfig = async function(self, config) {
const _postConfig = async () => {
// gets the initials configuration from the config
const initialsConfig = this.owner.initialsConfig(config);
// gets the 3d set from the config
const config3d = config["3d"] || {};
// unpacks scene options
const sceneOptions = config3d.scene ? this._unpackSceneOptions(config3d.scene) : {};
// unpacks initials options
const initialsOptions = initialsConfig ? this._unpackInitialsOptions(initialsConfig) : {};
// checks if initials are enabled
const initialsEnabled = this.owner.hasPersonalization();
// loads all the assets needed for this config
await this._loadCsrAssets(config, sceneOptions, initialsOptions);
// runs the process of applying the configuration defaults so everything
// can properly be built
this._initConfigDefaults({
rendererOptions: sceneOptions.rendererOptions || {},
cameraOptions: sceneOptions.cameraOptions || {},
zoomOptions: sceneOptions.zoomOptions || {},
enabledInitials: initialsEnabled,
initialsOptions: initialsOptions
});
// init scene
this._initScene();
// init the CSR initials
this._initCsrRenderedInitials();
// init debug tools
this._initDebug();
// renders newly build scene
this._render();
// flushes the complete set of operations that were waiting
// for the end of the pre-loading operation, notice that this
await this.flushPending(true);
self.loading = false;
this.trigger("ready", { id: this.id, origin: "configurator-csr" });
};
// runs synchronously or asynchronously depending on how the CSR configurator was setup
this.awaitPostConfig ? await _postConfig() : _postConfig();
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._registerElementHandlers = function() {
this._addElementHandler("mousedown", event => this._onMouseDown(this, event));
this._addElementHandler("mouseup", event => this._onMouseUp(this, event));
this._addElementHandler("mouseleave", event => this._onMouseLeave(this, event));
this._addElementHandler("mousemove", event => this._onMouseMove(this, event));
this._addElementHandler("wheel", event => this._onWheel(this, event));
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._registerInitialsGlobalHandlers = function() {
this._onInitialsBind = this.owner.bind("initials", (initials, engraving, params) =>
this._onInitials(this, initials, engraving, params)
);
this._onInitialsExtraBind = this.owner.bind("initials_extra", (initialsExtra, params) =>
this._onInitialsExtra(this, initialsExtra, params)
);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._unregisterInitialsGlobalHandlers = function() {
if (!this.owner) return;
if (this._onInitialsExtraBind) this.owner.unbind("initials_extra", this._onInitialsExtraBind);
if (this._onInitialsBind) this.owner.unbind("initials", this._onInitialsBind);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._registerConfigGlobalHandlers = function() {
this._onPreConfigBind = this.owner.bind("pre_config", (brand, model, options) =>
this._onPreConfig(this)
);
this._onPostConfigBind = this.owner.bind("post_config", config =>
this._onPostConfig(this, config)
);
};
/**
* @ignore
*/
ripe.ConfiguratorCsr.prototype._unregisterConfigGlobalHandlers = function() {
if (!this.owner) return;
if (this._onPostConfigBind) this.owner.unbind("post_config", this._onPostConfigBind);
if (this._onPreConfigBind) this.owner && this.owner.unbind("pre_config", this._onPreConfigBind);
};