Considerando o exemplo apresentado, eu posso dizer que a explicação é meio trivial.
Veja o grafo armengado a seguir:

Você vai perceber que F, não é “descendente” de D2, logo uma instância de F, não pode ser convertida para D2.
Também, uma instância de D2, não pode ser convertida para F, pois D2, não é “pai” de F.
O casting ocorre quando um objeto instanciado em uma classe Pai, assume a forma de um Filho (Polimorfismo).
Também ocorre um casting implícito quanto uma instância “filho” é passada como argumento de um método cujo parâmetro é a classe Pai (Polimorfismo de novo).
Não me vem outras ocorrências à mente. 
No “geral”:
Se você converter um “Pai para um filho”, da certo.
Se tentar converter “um filho para um Pai”, da erro.
No momento tem um porém que não tenho como explicar: quando se converte int para double e situações semelhantes, pois é passado para o programador a ciência de que pode haver perda no valor convertido.
Voltando ao tema proposto, se você perceber, tanto as instâncias de D3 quanto a de F, não podem ser convertidas para nenhuma outra classe, pois elas são “fim de linha”, ou seja, não são “genitores”, são só “filhas”.
Exemplo básico, você pode converter um List em ArrayList, mas não pode converter um ArrayList em um List.
Sugestão de vídeo a partir dos 25 minutos:
Ponto interessante:
Se você instanciar um objeto de A ou B, ou C, ou D, este objeto pode assumir a forma de qualquer ramificação de D, ou seja, você pode fazer um casting para A1, D1,D2 ou D3.
Assumo que minha explanação não é das melhores. 