@@ -128,7 +128,8 @@ int SurfReactMpex::react(Particle::OnePart *&ip,
128128
129129 // no secondaries in this model
130130 jp = nullptr ;
131- // let SurfCollideDiffuse::diffuse() modify the direction if we reflect
131+ // velreset toggled below: 1 if we set ip->v ourselves (reflected branch),
132+ // 0 if surf_collide should handle the leftover (stick branch with ip=nullptr).
132133 velreset = 0 ;
133134
134135 // If no reaction templates exist for this species, do nothing
@@ -166,15 +167,66 @@ int SurfReactMpex::react(Particle::OnePart *&ip,
166167 double u = random->uniform ();
167168
168169 if (u < RN ) {
169- // REFLECTION: keep ip alive; SurfCollideDiffuse::diffuse will
170- // randomize direction, but we can enforce the energy loss via RE.
171- if (RE > 0.0 ) {
172- double scale = sqrt (RE ); // v' = sqrt(RE) * v => E' = RE * E
173- ip->v [0 ] *= scale;
174- ip->v [1 ] *= scale;
175- ip->v [2 ] *= scale;
170+ // REFLECTION. We write the outgoing velocity directly here and tell
171+ // SPARTA's surf_collide to leave it alone (velreset = 1). Direction is
172+ // sampled from a cosine (Lambert) distribution in the outgoing half-
173+ // space; speed is set by the angle-dependent energy reflection
174+ // coefficient RE(theta): |v_out| = sqrt(RE) * |v_in|.
175+ velreset = 1 ;
176+
177+ // ---- local frame (n_hat, tan1, tan2) at the surface ----
178+ double n_hat[3 ];
179+ if (nlen > 0.0 ) {
180+ n_hat[0 ] = norm[0 ] / nlen;
181+ n_hat[1 ] = norm[1 ] / nlen;
182+ n_hat[2 ] = norm[2 ] / nlen;
183+ } else {
184+ n_hat[0 ] = 0.0 ; n_hat[1 ] = 0.0 ; n_hat[2 ] = 1.0 ;
176185 }
177- ip->ispecies = r->products [0 ];
186+
187+ // tangent1 = component of incoming v perpendicular to n_hat (then
188+ // normalised). Falls back to a random tangent for normal incidence.
189+ double dotvn = MathExtra::dot3 (v, n_hat);
190+ double tan1[3 ] = {
191+ v[0 ] - dotvn * n_hat[0 ],
192+ v[1 ] - dotvn * n_hat[1 ],
193+ v[2 ] - dotvn * n_hat[2 ]
194+ };
195+ if (MathExtra::lensq3 (tan1) < 1.0e-30 ) {
196+ tan1[0 ] = random->uniform ();
197+ tan1[1 ] = random->uniform ();
198+ tan1[2 ] = random->uniform ();
199+ double dt = MathExtra::dot3 (tan1, n_hat);
200+ tan1[0 ] -= dt * n_hat[0 ];
201+ tan1[1 ] -= dt * n_hat[1 ];
202+ tan1[2 ] -= dt * n_hat[2 ];
203+ }
204+ MathExtra::norm3 (tan1);
205+ double tan2[3 ];
206+ MathExtra::cross3 (n_hat, tan1, tan2);
207+ MathExtra::norm3 (tan2);
208+
209+ // ---- cosine-weighted (Lambert) hemisphere sample ----
210+ double xi1 = random->uniform ();
211+ double xi2 = random->uniform ();
212+ double cosT = sqrt (xi1); // p(cos theta) = 2 cos theta
213+ double sinT = sqrt (std::max (0.0 , 1.0 - cosT * cosT));
214+ double phi = 2.0 * MathConst::MY_PI * xi2;
215+ double cosP = cos (phi), sinP = sin (phi);
216+
217+ double dir[3 ] = {
218+ sinT * cosP * tan1[0 ] + sinT * sinP * tan2[0 ] + cosT * n_hat[0 ],
219+ sinT * cosP * tan1[1 ] + sinT * sinP * tan2[1 ] + cosT * n_hat[1 ],
220+ sinT * cosP * tan1[2 ] + sinT * sinP * tan2[2 ] + cosT * n_hat[2 ]
221+ };
222+
223+ // ---- speed from RE(theta) ----
224+ double speed = vlen * sqrt (std::max (0.0 , RE ));
225+ ip->v [0 ] = speed * dir[0 ];
226+ ip->v [1 ] = speed * dir[1 ];
227+ ip->v [2 ] = speed * dir[2 ];
228+
229+ ip->ispecies = r->products [0 ];
178230 nsingle++;
179231 tally_single[list[0 ]]++; // bookkeeping
180232
0 commit comments