OpenLB 1.7
Loading...
Searching...
No Matches
surfaceDetachment.h
Go to the documentation of this file.
1/* This file is part of the OpenLB library
2 *
3 * Copyright (C) 2022 Nicolas Hafen, Mathias J. Krause
4 * E-mail contact: info@openlb.net
5 * The most recent release of OpenLB can be downloaded at
6 * <http://www.openlb.net/>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public
19 * License along with this program; if not, write to the Free
20 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22*/
23
24
25/* Treatment of particle dymamics while detaching from surfaces
26 * - mainly used for wall-flow applicatio:
27 * https://gitlab.com/openlb/olb/-/blob/private/nicolas/main4/apps/nicolas/A_WallFlow/wallFlow/README.md
28 * - for now, mainly intended to be used in 3D
29 *
30*/
31
32#ifndef PARTICLE_SURFACE_DETACHMENT_H
33#define PARTICLE_SURFACE_DETACHMENT_H
34
35//#define VERBOSE_ADHESION_FORCE
36
37#include <cassert>
38
39namespace olb {
40
41namespace particles {
42
43namespace interaction {
44
45
46//Calculate 2D position on cylcic hull
47template<typename T>
48Vector<T,2> pos2DOnCyclicHull(Vector<T,2> position, T radius, T angle){
49 Vector<T,2> relPos( std::cos(angle-M_PI)*radius,
50 -std::sin(angle-M_PI)*radius );
51 return position + relPos;
52}
53
54//Eccentric position of position rotated around center of roateion (COR)
55template<typename T>
57 Vector<T,3>& position, Vector<T,3> relPosCOR,
58 T angle1DRad, unsigned axis, bool verbose=false)
59{
60 //Retrieve new absolute centre of rotation
61 Vector<T,3> CORnew = position+relPosCOR;
62 //Calculate radius of rotation
63 T radiusRot = norm(relPosCOR);
64 //Convert to 2D space
65 Vector<T,2> CORnew2D;
66 if (axis==0){
67 CORnew2D[0] = CORnew[1];
68 CORnew2D[1] = CORnew[2];
69 } else if (axis==1){
70 CORnew2D[0] = CORnew[0];
71 CORnew2D[1] = CORnew[2];
72 } else if (axis==2){
73 CORnew2D[0] = CORnew[0];
74 CORnew2D[1] = CORnew[1];
75 } else {
76 std::cerr << "ERROR: Unknown axis " << axis << "!" << std::endl;
77 }
78 //Retrieve position on cyclic hull
79 Vector<T,2> posEccentric2D = pos2DOnCyclicHull(CORnew2D,radiusRot,angle1DRad);
80 //Convert to 3D space
81 Vector<T,3> posEccentric;
82 if (axis==0){
83 posEccentric[0] = position[0];
84 posEccentric[1] = posEccentric2D[0];
85 posEccentric[2] = posEccentric2D[1];
86 } else if (axis==1){
87 posEccentric[0] = posEccentric2D[0];
88 posEccentric[1] = position[1];
89 posEccentric[2] = posEccentric2D[1];
90 } else if (axis==2){
91 posEccentric[0] = posEccentric2D[0];
92 posEccentric[1] = posEccentric2D[1];
93 posEccentric[2] = position[2];
94 } else {
95 std::cerr << "ERROR: Unknown axis " << axis << "!" << std::endl;
96 }
97 //Output
98 if (verbose){
99 OstreamManager clout(std::cout, "EccentricRot");
100 clout << "---Eccentric Position ---" << std::endl;
101 clout << "Position= " << position << std::endl;
102 clout << "RelPosCOR= " << relPosCOR << std::endl;
103 clout << "CORnew= " << CORnew << std::endl;
104 clout << "radiusRot= " << radiusRot << std::endl;
105 clout << "angle1DRad (repr. rotation at initial position)= " << angle1DRad << std::endl;
106 clout << "posEccentric2D= " << posEccentric2D << std::endl;
107 clout << "-------------------------" << std::endl;
108 }
109 return posEccentric;
110}
111
112//Get constant cuboid diagonal angle
113template<typename T>
115 return -std::atan2(-extent[2],extent[0]);
116}
117
118
119
120
121//Handle detachment
122// - Corrects eccentric particle position, and updates
123// offset of centre of rotation
124template<typename T, typename PARTICLETYPE>
126 Vector<T,PARTICLETYPE::d>& mainFlowDirection,
127 Particle<T,PARTICLETYPE>& particle )
128{
129 using namespace descriptors;
130 using namespace access;
131 static_assert(PARTICLETYPE::template providesNested<SURFACE,COR_OFFSET>(),
132 "Field SURFACE:COR_OFFSET has to be provided");
133 //Retrieve surface normal of surface closest to particle
134 auto surfaceNormal = boundaries::getNormalOnClosestSurface( wall, particle );
135 //Retrieve rotation axis for detachmant
136 auto rotationAxisNormal = crossProduct( surfaceNormal, mainFlowDirection );
137 unsigned rotationAxis = util::maxElementAbsPos( rotationAxisNormal );
138 //Calculate relative angle between particle and surface
139 auto normalParticleSurface = getSurfaceNormal( particle );
140 auto orientationRelative = util::angleBetweenVectors(
141 normalParticleSurface, surfaceNormal );
142 //Retrieve geometry specific angle offset
143 auto extent = getCuboidSurfaceExtent( particle );
144 //Retrieve current position and offset of center of rotation (COR)
145 auto position = getPosition( particle );
146 auto CORoffset = particle.template getField<SURFACE,COR_OFFSET>();
147 //Retrieve eccentric position
148 decltype(position) posEccentric;
149 if constexpr (is3D<PARTICLETYPE>()){
150 T constAngleOffset = getCuboid3DDiagonalAngle1D( extent );
151 //Calculate effective angle around rotationAxis (considering const offset)
152 T signedDirection = util::maxElementAbs(surfaceNormal);
153 T angleEffective1D = orientationRelative[rotationAxis]
154 + constAngleOffset * signedDirection;
155 //Calculate eccentric position of particle centre
156 posEccentric = eccentricPosition3D( position,
157 CORoffset, angleEffective1D, rotationAxis );
158 } else {
159 std::cerr << "ERROR: 2D version not implemented yet!" << std::endl;
160 }
161 //Calculate difference between original and new position and update CORoffset
162 auto posCubeDiffEcc = posEccentric-position;
163 CORoffset -= posCubeDiffEcc;
164 //Set new quantities to particle
165 particle.template setField<GENERAL,POSITION>( posEccentric );
166 particle.template setField<SURFACE,COR_OFFSET>( CORoffset );
167}
168
169template<typename T, unsigned D>
170void getDetachmentAxes( Vector<T,D> mainFlowDirection, Vector<T,D> surfaceNormal,
171 unsigned short& axisFlow, unsigned short& axisSurface, unsigned short& axisRot )
172{
173 axisFlow = util::maxElementAbsPos( mainFlowDirection );
174 axisSurface = util::maxElementAbsPos( surfaceNormal );
175 if (axisFlow==0){
176 if (axisSurface==1){ axisRot=2; }
177 else if (axisSurface==2){ axisRot=1; }
178 else { std::cerr << "Error: Axes not unique!" << std::endl; }
179 } else if (axisFlow==1){
180 if (axisSurface==0){ axisRot=2; }
181 else if (axisSurface==2){ axisRot=0; }
182 else { std::cerr << "Error: Axes not unique!" << std::endl; }
183 } else {
184 if (axisSurface==0){ axisRot=1; }
185 else if (axisSurface==1){ axisRot=0; }
186 else { std::cerr << "Error: Axes not unique!" << std::endl; }
187 }
188}
189
190
191
192template<typename T, typename PARTICLETYPE>
194 Vector<T,PARTICLETYPE::d>& mainFlowDirection,
195 Particle<T,PARTICLETYPE>& particle )
196{
197 using namespace particles::access;
198 using namespace descriptors;
199 //Retrieve cuboid extent
200 auto extent = getCuboidSurfaceExtent( particle );
201 //Retrieve surface normal of attached surface
202 auto surfaceNormal = boundaries::getNormalOnClosestSurface( wall, particle );
203 const unsigned short axisFlow = util::maxElementAbsPos( mainFlowDirection );
204 const unsigned short axisSurface = util::maxElementAbsPos( surfaceNormal );
205 const unsigned axisZunrotated = 2; //Assuming z-oriented flush placement
206 //Calculate COR
207 Vector<T,PARTICLETYPE::d> CORoffset(0.);
208 CORoffset[axisFlow] = .5*extent[axisFlow] * mainFlowDirection[axisFlow];
209 CORoffset[axisSurface] = -.5*extent[axisZunrotated] * surfaceNormal[axisSurface];
210 //Set COR offset
211 particle.template setField<SURFACE,COR_OFFSET>(CORoffset);
212}
213
214template<typename T, typename PARTICLETYPE>
216 Particle<T,PARTICLETYPE>& particle,
217 Vector<T,PARTICLETYPE::d>& mainFlowDirection )
218{
219 using namespace descriptors;
220 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,ACTIVE>(),
221 "Field DYNBEHAVIOUR:ACTIVE has to be provided");
222 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,DETACHING>(),
223 "Field DYNBEHAVIOUR:DETACHING has to be provided");
224 //Set COR
225 if constexpr (access::is3D<PARTICLETYPE>()){
226 setCORcuboid3Dflush( wall, mainFlowDirection, particle );
227 } else {
228 std::cerr << "ERROR: 2D version not implemented yet!" << std::endl;
229 }
230 //Set detaching and active state
231 particle.template setField<DYNBEHAVIOUR,DETACHING>(true);
232 particle.template setField<DYNBEHAVIOUR,ACTIVE>( false );
233 //Set contact state if provided
234 if constexpr(access::providesComputeContact<PARTICLETYPE>()){
235 particle.template setField<DYNBEHAVIOUR,COMPUTE_CONTACT>(false);
236 }
237}
238
239//TODO: for now only for cuboid 3D
240//INFO: Prototype version which leads to particle oscillation.
241template<typename T, typename PARTICLETYPE>
243 Vector<T,PARTICLETYPE::d>& mainFlowDirection,
244 Particle<T,PARTICLETYPE>& particle )
245{
246 using namespace particles::access;
247 using namespace descriptors;
248 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,DETACHING>(),
249 "Field DYNBEHAVIOUR:DETACHING has to be provided");
250 static_assert(PARTICLETYPE::template providesNested<SURFACE,COR_OFFSET>(),
251 "Field SURFACE:COR_OFFSET has to be provided");
252 //Retrieve surface normal and distance of surface closest to particle
253 auto surfaceNormal = boundaries::getNormalOnClosestSurface( wall, particle );
254 //Retrieve cuboid surface extent
255 auto extent = getCuboidSurfaceExtent( particle );
256 const unsigned axisZunrotated = 2; //Assuming z-oriented flush placement
257 T offsetCentreWall = .5*extent[axisZunrotated];
258 //Retrieve current CORoffset for force lever
259 auto CORoffset = getCORoffset( particle );
260 //Evaluate axes
261 const unsigned short axisFlow = util::maxElementAbsPos( mainFlowDirection );
262 const unsigned short axisSurface = util::maxElementAbsPos( surfaceNormal );
263
264 T surfaceDistance = -CORoffset[axisSurface]-offsetCentreWall;
265 T lever = CORoffset[axisFlow];
266
267 //Retrieve torque
268 Vector<T,3> torque = getTorque( particle );
269 Vector<T,3> force = getForce( particle );
270
271 //TODO: adapt hardcoded axis
272 unsigned short rotAxis = 1;
273
274 //Retrieve normal and tangential adhesion component of particle.
275 Vector<T,2> adhesion = getAdhesion( particle );
276 T adhesionNormal = adhesion[0];
277 T adhesionTangential = adhesion[1];
278
279 //When distance greater than 500 Angstroem (for now random)
280 //TODO: should be depending on Lennard-Jones Potential as well
281 T fullForceDist = 500e-10;
282
283 //Only consider force up fullForceDist
284 if (surfaceDistance <= fullForceDist){
285
286 //Ensure force to only act as adhesion and not as repulsion
287 if (surfaceDistance > 0){
288
289 //TODO: linear for now. Should be Lennard-Jones instead
290 T distFactor = surfaceDistance/fullForceDist;
291
292 //Calculate force and torque contributions due to adhesion
293 T addhesionForceNormalContribution = -distFactor*adhesionNormal;
294 T adhesionTorqueContribution = addhesionForceNormalContribution*lever;
295
296 //Calculate new force and torque
297 force[axisSurface] += addhesionForceNormalContribution * surfaceNormal[axisSurface];
298 torque[rotAxis] += adhesionTorqueContribution;
299
300 //Apply new force and torque
301 particle.template setField<FORCING,FORCE>( force );
302 particle.template setField<FORCING,TORQUE>( torque );
303
304#ifdef VERBOSE_ADHESION_FORCE
305 auto orient = getAngle(particle);
306 std::cout << "Adhesion:" << std::endl
307 << " - distance=" << surfaceDistance
308 << " (factor=" << (distFactor) << ")"
309 << ", angle=" << orient[1] << std::endl
310 << " - forceNormalContrib=" << addhesionForceNormalContribution
311 << ", torqueContrib=" << adhesionTorqueContribution << std::endl
312 << " - resForce=" << force[axisSurface]
313 << ", resTorque=" << torque[rotAxis] << std::endl
314 << " - resForce3D=" << force
315 << ", resTorque3D=" << torque
316 << std::endl;
317#endif
318
319 }
320 }
321}
322
324template<typename T, typename PARTICLETYPE>
326 Vector<T,PARTICLETYPE::d>& surfaceNormal, Vector<T,PARTICLETYPE::d>& mainFlowDirection)
327{
328 using namespace particles::access;
329 using namespace descriptors;
330 static_assert(PARTICLETYPE::template providesNested<SURFACE,COR_OFFSET>(),
331 "Field SURFACE:COR_OFFSET has to be provided");
332
333 //Calculate tilt axis
334 Vector<T,3> tiltAxis = crossProduct( mainFlowDirection, surfaceNormal );
335 unsigned axisRot = util::maxElementAbsPos(tiltAxis);
336 unsigned axisFlow = util::maxElementAbsPos(mainFlowDirection);
337 //Retrieve particle torque
338 auto torque = getTorque(particle);
339 //Calculate tilt torque
340 T torqueTilt = torque[axisRot]*-tiltAxis[axisRot];
341 //Retrieve lever by COR offset
342 auto CORoffset = getCORoffset( particle );
343 T lever = CORoffset[axisFlow];
344 //Calculate rotation induced normal force (ensure no division by zero)
345 T forceNormalRot = 0.;
346 if (lever!=0){ forceNormalRot = torqueTilt / lever; }
347 //Return rotation induced normal force
348 return forceNormalRot;
349}
350
351
353template<typename T, typename PARTICLETYPE>
355 Vector<T,PARTICLETYPE::d>& mainFlowDirection,
356 Particle<T,PARTICLETYPE>& particle )
357{
358 using namespace particles::access;
359 using namespace descriptors;
360 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,ACTIVE>(),
361 "Field DYNBEHAVIOUR:ACTIVE has to be provided");
362 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,DETACHING>(),
363 "Field DYNBEHAVIOUR:DETACHING has to be provided");
364 static_assert(PARTICLETYPE::template providesNested<SURFACE,COR_OFFSET>(),
365 "Field SURFACE:COR_OFFSET has to be provided");
366
367 //Retrieve surface normal of surface closest to particle
368 auto surfaceNormal = boundaries::getNormalOnClosestSurface( wall, particle );
369 //Retrieve current adhesion (normal component)
370 Vector<T,2> forceAdhesion = getAdhesion( particle );
371 T forceAdhesionN = forceAdhesion[0];
372 //Calculate rotation induced normal force
373 T forceNormalRot = getRotationInducedNormalForce( particle, surfaceNormal, mainFlowDirection );
374 //Evaluate threshold
375 if (forceNormalRot>forceAdhesionN){
376 //Set active, when passing threshold once
377 particle.template setField<DYNBEHAVIOUR,ACTIVE>( true );
378 return false;
379 } else {
380 return true;
381 }
382}
383
384
385
386template<typename T, typename PARTICLETYPE>
388 Particle<T,PARTICLETYPE>& particle,
389 T tiltThreshold = 0.3*M_PI )
390{
391 using namespace particles::access;
392 using namespace descriptors;
393 static_assert(PARTICLETYPE::template providesNested<DYNBEHAVIOUR,DETACHING>(),
394 "Field DYNBEHAVIOUR:DETACHING has to be provided");
395 static_assert(PARTICLETYPE::template providesNested<SURFACE,COR_OFFSET>(),
396 "Field SURFACE:COR_OFFSET has to be provided");
397 //Retrieve position and radius
398 auto position = getPosition( particle );
399 auto radius = getRadius( particle );
400 //Retrieve relative orientation to surface
401 auto normalParticleSurface = getSurfaceNormal( particle );
402 auto orientationRelative = boundaries::getRelativeSurfaceOrientation(
403 wall, position, normalParticleSurface, radius );
404 //Evaluate tilt intensity
405 T tiltIntensity = util::max_element(abs( orientationRelative ));
406 if ( tiltIntensity > tiltThreshold){ //For now, random condition here
407 //Set state (standard verlet integration with collision)
408 particle.template setField<DYNBEHAVIOUR,DETACHING>( false );
409 //TODO: Generalize call as detachment is not directly connected to contact
410 if constexpr( providesComputeContact<PARTICLETYPE>() ){
411 particle.template setField<DYNBEHAVIOUR,COMPUTE_CONTACT>( true );
412 }
413 //Reset centre of rotation
414 particle.template setField<SURFACE,COR_OFFSET>( Vector<T,PARTICLETYPE::d>(0) );
415 }
416}
417
418
420// - assuming that particle activity can be measured by its kinetic energy, as long
421// not undergoing the inflection point (eKin=0) during collision. In this case,
422// however, the particle experiences a significant force, which is therefore
423// evaluated as well. When both force and kinetic energy are negligible, particle
424// is deactivated after the predefined physical time.
425template<typename T, typename PARTICLETYPE, typename DESCRIPTOR>
427 Particle<T,PARTICLETYPE>& particle,
428 UnitConverter<T,DESCRIPTOR> const& converter,
429 T forceAbsoluteThreshold, T kinEnergyThreshold,
430 T timeNoActiveThreshold, std::size_t iTinterval=1 )
431{
432 using namespace descriptors;
433 //Retrieve force and kinetic energy
434 auto force = access::getForce( particle );
435 T forceAbs = norm(force);
436 T kinEnergy = dynamics::calcKineticEnergy( particle );
437 //Evaluate force threshold
438 if (forceAbs<forceAbsoluteThreshold){
439 //Evaluate kinetic energy threshold
440 if (kinEnergy<kinEnergyThreshold){
441 //Retrieve deactive counter
442 std::size_t counterDeactive =
443 particle.template getField<DYNBEHAVIOUR,COUNTER<ACTIVE>>();
444 //Increment deactive counter and calculate no no active time
445 counterDeactive+=iTinterval;
446 T noActiveTime = converter.getPhysTime( counterDeactive );
447 //If no active time not passed
448 if (noActiveTime < timeNoActiveThreshold){
449 //Write counter
450 particle.template setField<DYNBEHAVIOUR,COUNTER<ACTIVE>>(counterDeactive);
451 } else {
452 //If time passed, deactivate particle
453 particle.template setField<DYNBEHAVIOUR,ACTIVE>(false);
454 if constexpr( access::providesComputeContact<PARTICLETYPE>() ){
455 particle.template setField<DYNBEHAVIOUR,COMPUTE_CONTACT>(false);
456 }
457 return true;
458 } //if (noActiveTime < noActiveTimeDeactivate)
459 } else {
460 //If kinetic energy threshold not passed, reset counter
461 particle.template setField<DYNBEHAVIOUR,COUNTER<ACTIVE>>(0);
462 } //if (kinEnergy<eKinThres)
463 }
464 return false;
465}
466
467
468
469
470} //namespace interaction
471
472} //namespace particles
473
474} //namespace olb
475
476
477#endif
#define M_PI
class for marking output with some text
Conversion between physical and lattice units, as well as discretization.
constexpr T getPhysTime(size_t latticeTime) const
conversion from lattice to physical time
Plain old scalar vector.
Definition vector.h:47
Vector< T, PARTICLETYPE::d > getForce(Particle< T, PARTICLETYPE > particle)
Vector< T, D > getNormalOnClosestSurface(SolidBoundary< T, D > &solidBoundary, Vector< T, D > &position, T referenceLength)
Vector< T, utilities::dimensions::convert< D >::rotation > getRelativeSurfaceOrientation(SolidBoundary< T, D > &solidBoundary, Vector< T, D > &position, Vector< T, D > &normalToBeComparedTo, T referenceLength)
T calcKineticEnergy(Particle< T, PARTICLETYPE > &particle)
bool checkAdhesion(SolidBoundary< T, PARTICLETYPE::d > &wall, Vector< T, PARTICLETYPE::d > &mainFlowDirection, Particle< T, PARTICLETYPE > &particle)
Check adhesion and return true if still adhering.
Vector< T, 3 > eccentricPosition3D(Vector< T, 3 > &position, Vector< T, 3 > relPosCOR, T angle1DRad, unsigned axis, bool verbose=false)
void getDetachmentAxes(Vector< T, D > mainFlowDirection, Vector< T, D > surfaceNormal, unsigned short &axisFlow, unsigned short &axisSurface, unsigned short &axisRot)
void evaluateDetachmentState(SolidBoundary< T, PARTICLETYPE::d > &wall, Particle< T, PARTICLETYPE > &particle, T tiltThreshold=0.3 *M_PI)
void handleDetachment(SolidBoundary< T, PARTICLETYPE::d > &wall, Vector< T, PARTICLETYPE::d > &mainFlowDirection, Particle< T, PARTICLETYPE > &particle)
void initializeDetachment(SolidBoundary< T, PARTICLETYPE::d > &wall, Particle< T, PARTICLETYPE > &particle, Vector< T, PARTICLETYPE::d > &mainFlowDirection)
T getCuboid3DDiagonalAngle1D(Vector< T, 3 > &extent)
Vector< T, 2 > pos2DOnCyclicHull(Vector< T, 2 > position, T radius, T angle)
void applyAdhesionForce(SolidBoundary< T, PARTICLETYPE::d > &wall, Vector< T, PARTICLETYPE::d > &mainFlowDirection, Particle< T, PARTICLETYPE > &particle)
T getRotationInducedNormalForce(Particle< T, PARTICLETYPE > &particle, Vector< T, PARTICLETYPE::d > &surfaceNormal, Vector< T, PARTICLETYPE::d > &mainFlowDirection)
Calculation of rotation induced normal force.
bool checkParticleReDeposition(Particle< T, PARTICLETYPE > &particle, UnitConverter< T, DESCRIPTOR > const &converter, T forceAbsoluteThreshold, T kinEnergyThreshold, T timeNoActiveThreshold, std::size_t iTinterval=1)
Check particle re-deposition.
void setCORcuboid3Dflush(SolidBoundary< T, PARTICLETYPE::d > &wall, Vector< T, PARTICLETYPE::d > &mainFlowDirection, Particle< T, PARTICLETYPE > &particle)
T angleBetweenVectors(const Vector< T, 2 > &a, const Vector< T, 2 > &b)
Calculates angles between two 2D vectors.
T max_element(const Vector< T, Size > &a)
finds maximum element of all elements
T maxElementAbs(const Vector< T, Size > &a)
finds maximum element of all absolute elements
unsigned maxElementAbsPos(const Vector< T, Size > &a)
finds position of maximum element of all absolute elements
Top level namespace for all of OpenLB.
std::enable_if_t< std::is_arithmetic< T >::type::value, T > abs(T x)
Definition util.h:396
constexpr T norm(const ScalarVector< T, D, IMPL > &a)
Euclidean vector norm.
auto crossProduct(const ScalarVector< T, D, IMPL > &a, const ScalarVector< T, D, IMPL_ > &b)
Definition vector.h:235