source: js/human_3d_alignment/src/visualization/transformations.js @ 881

Last change on this file since 881 was 881, checked in by Maciej Komosinski, 5 years ago

Initial, prototype version of a javascript app to test how humans align two 3D structures

File size: 14.4 KB
Line 
1/*global Module*/
2"use strict";
3
4import * as THREE from 'three';
5import JointMeshFactory from './jointmeshfactory';
6import PartMeshFactory from './partmeshfactory';
7
8/**@typedef {object} BodyElement
9 * @property {boolean} isBodyElement checked if element is body element
10 * @property {string} type should be 'p' for parts, 'j' for joints
11 * @property {object} data model logic for part or joint
12 * @property {Mesh} mesh mesh for a given part
13*/
14
15/**@typedef {BodyElement} PartMesh
16 * @property {Module.Part} data model logic for part
17 * @property {Module.Joint[]} connectedJoints list of joints connected to part
18 */
19
20/**@typedef {BodyElement} JointMesh
21 * @property {Module.Joint} data reference to model logic joint
22 * @property {Module.Part[]} connectedParts list of parts connected by this joint
23 * @property {boolean} showTransparent true if joint should be transparent
24 */
25
26/**
27 * @typedef {object} ShapeInfo
28 * @property {string} name full name of shape
29 * @property {number} value enum for part shape
30 */
31
32/** Parts and Joints geometry info for viewer */
33export const geometry = {
34    part: {
35        defaultShape: {
36            radius: 0.213,
37            segments: 16
38        },
39        ellipsoidShape: {
40            radius: 1,
41            segments: 32
42        }
43    },
44    joint: {
45        cylinderShape: {
46            radius: 0.1,
47            radiusSegments: 10,
48            isTransparent: false,
49            opacity: 1
50        },
51        linkShape: {
52            radius: 0.04,
53            radiusSegments: 10,
54            isTransparent: true,
55            opacity: 0.2
56        }
57    }
58};
59
60/**
61 * Class for creating various transformations on Framsticks-SDK joints and parts
62 * meshes. It contains helpers for conversions between Joint/Part logic and THREE.js
63 * visualization.
64 *
65 * Any constants and methods considering shapes should go here.
66 */
67class Transformations {
68
69    /**
70     * Basic constructor for Transformations class.
71     * Initializes fields partShapes and jointShapes with getPartShapes and
72     * getJointsShapes results.
73     */
74    constructor() {
75        this.partShapes = this.getPartShapes();
76        this.jointShapes = this.getJointShapes();
77    }
78
79    /**
80     * Getter for Part shapes informations.
81     * @returns {ShapeInfo[]} basic info about shapes of parts
82     */
83    getPartShapes() {
84        let shapes = [];
85        shapes["SHAPE_BALL_AND_STICK"] = { name: "Ball & Stick", value: Module.Part["SHAPE_BALL_AND_STICK"] };
86        shapes["SHAPE_ELLIPSOID"] = { name: "Elipsoid", value: Module.Part["SHAPE_ELLIPSOID"] };
87        shapes["SHAPE_CUBOID"] = { name: "Cuboid", value: Module.Part["SHAPE_CUBOID"] };
88        shapes["SHAPE_CYLINDER"] = { name: "Cylinder", value: Module.Part["SHAPE_CYLINDER"] };
89        return shapes;
90    }
91
92    /**
93     * Getter for Joint shapes informations.
94     * @returns {ShapeInfo[]} basic info about shapes of joints
95     */
96    getJointShapes() {
97        let shapes = [];
98        shapes["SHAPE_BALL_AND_STICK"] = { name: "Ball & Stick", value: Module.Joint["SHAPE_BALL_AND_STICK"] };
99        shapes["SHAPE_FIXED"] = { name: "Fixed", value: Module.Joint["SHAPE_FIXED"] };
100        return shapes;
101    }
102
103    /**
104     * Recursive method for checking which elements are affected by transformations of object.
105     * This is internal method that should not be called.
106     * @param {BodyElement} bodyElement bodyelement, for which connected elements are checked for affection
107     * @param {object} result {partElements: PartMesh[], jointElements: JointMesh[]} that is result of recursive call
108     */
109    getAffectedElements_r(bodyElement, result) {
110        if (bodyElement.marked) {
111            return;
112        }
113
114        if (bodyElement.type == "p") {
115
116            bodyElement.marked = true;
117            result.partElements.push(bodyElement);
118
119            for (let i = 0; i < bodyElement.connectedJoints.length; ++i) {
120                let joint = bodyElement.connectedJoints[i];
121                if (!joint.marked) {
122                    joint.marked = true;
123                    result.jointElements.push(joint);
124                    if (joint.data.get_usedelta()) {
125                        this.getAffectedElements_r(joint.connectedParts[1], result);
126                    }
127                }
128            }
129
130        }
131        else if (bodyElement.type == "j") {
132            bodyElement.marked = true;
133            result.jointElements.push(bodyElement);
134
135            this.getAffectedElements_r(bodyElement.connectedParts[1], result);
136        }
137    }
138
139    /**
140     * Function that finds affected elements with function getAffectedElements_r
141     * and returns all affected parts and joints
142     * @param {BodyElement} bodyElement bodyelement, for which connected elements are checked for affection
143     * @returns {object} {partElements: PartMesh[], jointElements: JointMesh[]} that is result of recursive call
144     */
145    getAffectedElements(bodyElement) {
146        let result = {
147            partElements: [],
148            jointElements: []
149        };
150
151        this.getAffectedElements_r(bodyElement, result);
152
153        for (let j = 0; j < result.partElements.length; ++j) {
154            result.partElements[j].marked = false;
155        }
156        for (let k = 0; k < result.jointElements.length; ++k) {
157            result.jointElements[k].marked = false;
158        }
159        return result;
160    }
161
162    /**
163     * Helper for clamping colors from Framsticks-SDK to 0-1 values
164     * @param {number} value density of color
165     * @returns {number} clamped color
166     */
167    calcColorComponent(value) {
168        return THREE.Math.clamp(value, 0, 1);
169    }
170
171    /**
172     * Applies color for a given element
173     * @param {BodyElement} bodyElement body element to color
174     * @param {number} component which component should be changed
175     * @param {number} value new value of component
176     */
177    applyColor(bodyElement, component, value) {
178        bodyElement.mesh.material.color[component] = this.calcColorComponent(value);
179    }
180
181    /**
182     * Creates new phong material for Mesh
183     * @param {number} r color red
184     * @param {number} g color green
185     * @param {number} b color blue
186     * @returns {MeshPhongMaterial} new material for Mesh
187     */
188    getNewMaterial(r, g, b) {
189        let color = new THREE.Color(
190            this.calcColorComponent(r),
191            this.calcColorComponent(g),
192            this.calcColorComponent(b));
193        return new THREE.MeshPhongMaterial({ color: color });
194    }
195
196    /**
197     * Returns THREE.js object representing rotation matrix based on XYZ axis
198     * rotation angles.
199     * @param {number} rotAngleX angle of rotation for X axis
200     * @param {number} rotAngleY angle of rotation for Y axis
201     * @param {number} rotAngleZ angle of rotation for Z axis
202     * @returns {THREE.Euler} rotation matrix to be applied for model
203     */
204    getPartRotation(rotAngleX, rotAngleY, rotAngleZ) {
205        return new THREE.Euler(rotAngleX, -rotAngleY, rotAngleZ, "ZYX");
206    }
207
208    /**
209     * Creates rotation matrix for cylinder Part
210     * @param {number} rotAngleX rotate coordinate
211     * @param {number} rotAngleY rotate coordinate
212     * @param {number} rotAngleZ rotate coordinate
213     * @returns {Matrix4} rotation matrix for cylinder Part
214     */
215    getCylinderPartRotationMatrix(rotAngleX, rotAngleY, rotAngleZ) {
216        let rot = this.getPartRotation(rotAngleX, rotAngleY, rotAngleZ);
217
218        let m = new THREE.Matrix4();
219        m.makeRotationZ(THREE.Math.degToRad(90));
220
221        let m2 = new THREE.Matrix4();
222        m2.makeRotationFromEuler(rot);
223        m2.multiply(m);
224        return m2;
225    }
226
227    /**
228     * Applies rotation on a single part
229     * @param {BodyElement} bodyElement element on which rotation will be performed
230     */
231    applySinglePartRotation(bodyElement) {
232        let shape = bodyElement.data.get_shape();
233        let rot = bodyElement.data.get_o().getAngles();
234
235        if (this.partShapes['SHAPE_CYLINDER'].value == shape) {
236            let m = this.getCylinderPartRotationMatrix(
237                rot.get_x(),
238                rot.get_y(),
239                rot.get_z());
240            bodyElement.mesh.rotation.setFromRotationMatrix(m);
241        }
242        else {
243            let r = this.getPartRotation(rot.get_x(), rot.get_y(), rot.get_z());
244            bodyElement.mesh.rotation.copy(r);
245        }
246    }
247
248    /**
249     * Applies rotation on a bodyElement part and all other affected elements.
250     * @param {BodyElement} bodyElement element on which rotation will be performed
251     */
252    applyPartRotation(bodyElement) {
253
254        let affectedElements = this.getAffectedElements(bodyElement);
255
256        if (affectedElements.partElements.length > 1) {
257            for (let i = 0; i < affectedElements.partElements.length; ++i) {
258                let part = affectedElements.partElements[i];
259                part.mesh.position.x = part.data.get_p().get_x();
260                part.mesh.position.y = part.data.get_p().get_y();
261                part.mesh.position.z = part.data.get_p().get_z();
262                this.applySinglePartRotation(part);
263            }
264            let jointMeshFactory = new JointMeshFactory(this.jointShapes);
265            for (let j = 0; j < affectedElements.jointElements.length; ++j) {
266                let newJointMesh = jointMeshFactory.create(affectedElements.jointElements[j].data);
267                this.changeBodyElementMesh(affectedElements.jointElements[j], newJointMesh);
268            }
269        } else {
270            this.applySinglePartRotation(bodyElement);
271        }
272
273    }
274
275    /**
276     * Redefines all affected joints due to part changes
277     * @param {BodyElement} bodyElement starting element
278     */
279    applyJointDeltaChange(bodyElement) {
280
281        let affectedElements = this.getAffectedElements(bodyElement);
282
283        for (let i = 0; i < affectedElements.partElements.length; ++i) {
284            let part = affectedElements.partElements[i];
285            part.mesh.position.x = part.data.get_p().get_x();
286            part.mesh.position.y = part.data.get_p().get_y();
287            part.mesh.position.z = part.data.get_p().get_z();
288            this.applySinglePartRotation(part);
289        }
290        let jointMeshFactory = new JointMeshFactory(this.jointShapes);
291        for (let j = 0; j < affectedElements.jointElements.length; ++j) {
292            let newJointMesh = jointMeshFactory.create(affectedElements.jointElements[j].data);
293            this.changeBodyElementMesh(affectedElements.jointElements[j], newJointMesh);
294        }
295
296    }
297
298    /**
299     * Applies scale to a part
300     * @param {BodyElement} bodyElement element to be scaled
301     */
302    applyPartScale(bodyElement) {
303        let part = bodyElement.data;
304        let shape = part.get_shape();
305        if (this.partShapes['SHAPE_CYLINDER'].value == shape) {
306            bodyElement.mesh.scale.set(
307                part.get_scale().get_y(),
308                part.get_scale().get_x(),
309                part.get_scale().get_z());
310        } else if (this.partShapes['SHAPE_BALL_AND_STICK'].value != shape) {
311            bodyElement.mesh.scale.set(
312                part.get_scale().get_x(),
313                part.get_scale().get_y(),
314                part.get_scale().get_z());
315        }
316    }
317
318    /**
319     * Replaces old mesh of an element with a new mesh
320     * @param {BodyElement} element Element to be changed
321     * @param {Mesh} newMesh New mesh for element
322     */
323    changeBodyElementMesh(element, newMesh) {
324        let scene = element.mesh.parent;
325        let oldMesh = element.mesh;
326
327        let userData = oldMesh.userData;
328        userData.showTransparent = newMesh.userData.showTransparent;
329        userData.mesh = newMesh.userData.mesh;
330        newMesh.userData = userData;
331
332        scene.remove(oldMesh);
333        scene.add(newMesh);
334
335        oldMesh.geometry.dispose();
336        oldMesh.geometry = null;
337        oldMesh.material.dispose();
338        oldMesh.material = null;
339    }
340
341    /**
342     * Changes Part position.
343     * @param {BodyElement} bodyElement element to be changed
344     * @param {string} axis which axis should be changed
345     */
346    applyPartPosition(bodyElement, axis) {
347        let jointMeshFactory = new JointMeshFactory(this.jointShapes);
348
349        let affectedElements = this.getAffectedElements(bodyElement);
350        for (let i = 0; i < affectedElements.partElements.length; ++i) {
351            affectedElements.partElements[i].mesh.position[axis] =
352                affectedElements.partElements[i].data.get_p()["get_" + axis]();
353        }
354        for (let j = 0; j < affectedElements.jointElements.length; ++j) {
355            let newJointMesh = jointMeshFactory.create(affectedElements.jointElements[j].data);
356            this.changeBodyElementMesh(affectedElements.jointElements[j], newJointMesh);
357        }
358    }
359
360    /**
361     * Updates all affected elements positions.
362     * @param {BodyElement} bodyElement beginning element
363     */
364    updateAfterPartTranslation(bodyElement) {
365        let jointMeshFactory = new JointMeshFactory(this.jointShapes);
366
367        let affectedElements = this.getAffectedElements(bodyElement);
368        for (let i = 0; i < affectedElements.partElements.length; ++i) {
369            let currentElement = affectedElements.partElements[i];
370            if (currentElement !== bodyElement) {
371                affectedElements.partElements[i].mesh.position.x =
372                    affectedElements.partElements[i].data.get_p().get_x();
373                affectedElements.partElements[i].mesh.position.y =
374                    affectedElements.partElements[i].data.get_p().get_y();
375                affectedElements.partElements[i].mesh.position.z =
376                    affectedElements.partElements[i].data.get_p().get_z();
377            }
378        }
379        for (let j = 0; j < affectedElements.jointElements.length; ++j) {
380            let newJointMesh = jointMeshFactory.create(affectedElements.jointElements[j].data);
381            this.changeBodyElementMesh(affectedElements.jointElements[j], newJointMesh);
382        }
383    }
384
385    /**
386     * Creates Part shape and removes old shape.
387     * @param {PartMesh} bodyElement part to be applied
388     */
389    applyPartShape(bodyElement) {
390        let partMeshFactory = new PartMeshFactory(this.partShapes);
391        let newPartMesh = partMeshFactory.create(bodyElement.data);
392        this.changeBodyElementMesh(bodyElement, newPartMesh);
393    }
394
395    /**
396     * Creates Joint shape and removes old shape.
397     * @param {JointMesh} bodyElement joint to be applied
398     */
399    applyJointShape(bodyElement) {
400        let jointMeshFactory = new JointMeshFactory(this.jointShapes);
401        let newJointMesh = jointMeshFactory.create(bodyElement.data);
402        this.changeBodyElementMesh(bodyElement, newJointMesh);
403    }
404}
405
406export default Transformations;
Note: See TracBrowser for help on using the repository browser.