g | x | w | all
Bytes Lang Time Link
935Python3220310T182137ZAjax1234
141Charcoal220310T005921ZNeil

Python3, 935 bytes:

E=enumerate
S=lambda b,x:(x+1==len(b)or x==0)*[1,-1][x>0]
def F(b,C,v,x,y,q,w,c=0):
 if int==type(b[x][y])and c:b[x][y]=[v,'R'][C==(x,y)];return
 if b[x][y]=='O':b[C[0]][C[1]]='H';return
 T=[]
 for i in[1,-1]:
  X=x+(i*(q==0)+(q!=0)*q);Y=y+(i*(w==0)+(w!=0)*w)
  if X>=0 and Y>=0:
   try:T+=[i]*(b[X][Y]=='O')
   except:1
 T*=b[x+q][y+w]=='.'
 if not c and T:b[C[0]][C[1]]='R';return
 if T:
  Q,W=0,0
  if len(T)==2:Q=q-1*(q!=0)*q;W=w-1*(w!=0)*w
  else:Q=(q==0)*T[0]*-1;W=(w==0)*T[0]*-1
  F(b,C,v,x,y,Q,W,c+1);return
 F(b,C,v,x+q,y+w,q,w,c+1)
def g(b):
 for x,j in E(b):
  for y,k in E(j):
   if k and type(k)==int:F(b,t:=(x,y),b[x][y],*t,S(b,x),S(b[0],y))
 return b
K=lambda b:'\n'.join(''.join(map(str,[[j,' '][y in[0,len(a)-1]]for y,j in E(a)]if i in[0,len(b)-1]else[[' ',j][y in[0,len(a)-1]]for y,j in E(a)]))for i,a in E(b))
f=lambda b,c=0:K(g((A:=[[0]+[(c:=c+1)for _ in b[0]]+[0]])+[[(M:=(c:=c+1))]+i+[M]for i in b]+eval(str(A))))

Try it online!

Charcoal, 171 141 bytes

≔⁺⁺ψ⭆θ ψη⊞υηWS⊞υ⪫  ιυη≔⁰ζF⊕LυFLη«Jκι¿⁼ KK«≔⁻⁶⊗⌕KVωδM✳δW⁼KK.«≔⊗E²№KD²✳⁺δ⁺²×⁴μOε≧⁺⊗⌈εδM✳δ≧⁻↨ε±¹δ»≡KKO≔Hδ¿∧⁼ⅈκ⁼ⅉι≔Rδ«≔Iζδδ≦⊕ζ»Jκιδ»»J¹¦¹UOLθ⊖Lυ 

Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated strings. Explanation:

≔⁺⁺ψ⭆θ ψη⊞υη

Create a header row.

WS⊞υ⪫  ι

Read the grid and pad the sides.

υη

Output the grid and a footer.

≔⁰ζ

Start with no rays.

F⊕LυFLη«

Loop over all of the squares.

Jκι¿⁼ KK«

Is this an entry point?

≔⁻⁶⊗⌕KVωδ

Work out which direction the ray leaves this entry point.

M✳δ

Move onto the grid.

W⁼KK.«

Repeat while the current cell is empty.

≔⊗E²№KD²✳⁺δ⁺²×⁴μOε

Check the neighbours for Os.

≧⁺⊗⌈εδ

If there are any then reverse direction so that the next move takes a step back. (This also handles the case of an O at the edge of the grid.)

M✳δ

Take one step.

≧⁻↨ε±¹δ

Adjust the direction if there was only one adjacent O.

»≡KKO≔Hδ

Did we hit an atom?

¿∧⁼ⅈκ⁼ⅉι≔Rδ

Were we reflected?

«≔Iζδδ≦⊕ζ»

Otherwise, output the next ray number at the exit point we just reached.

Jκιδ

Output the result of the ray at its entry point.

»»J¹¦¹UOLθ⊖Lυ 

Clear out the grid.