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

Last change on this file since 911 was 911, checked in by Maciej Komosinski, 4 years ago

Added the actual functionality of the app in place of previous draft

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