忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

Three.dartで「Exception: type 'int' is not a subtype of type 'double' of 'value'. (package:three/src/renderers/web_gl_renderer.dart:23)」となる問題

ここ数週間、今まで使えていたThree.dartが謎の例外を投げて動かなくなる問題にハマっていたので解決法を書いておきます。ネットで軽くググった範囲では他の人がこの問題で困っているのが見つからなかったので私の環境だけかもしれませんが。

WebGLRendererが例外を投げて全く動かなくなるのですが、WebGLRendererのコンストラクタに渡すパラメーターを一つ追加するだけできちんと動くようになります。


問題

2016/02/25現在のThree.dartでは、次のような立方体を描くプログラムを動かそうとしても例外が発生して何も描かれません。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>three.dart sample</title>
</head>
<body>
    <canvas id="myCanvas" width="500" height="500"></canvas>
    <script type="application/dart" src="main.dart"></script>
    <script src="packages/browser/dart.js"></script>
</body>
</html>

main.dart

import 'dart:html';
import 'package:three/three.dart';

PerspectiveCamera camera;
Scene scene;
WebGLRenderer renderer;

Mesh cube;

void main() {
  init();
  animate(0);
}

void init() {
  CanvasElement canvas = querySelector("#myCanvas");
  
  scene = new Scene();
  camera = new PerspectiveCamera(
      70.0, 
      canvas.width / canvas.height, 
      1.0, 
      1000.0
      );

  camera.position.z = 400.0;
  scene.add(camera);

  var geometry = new CubeGeometry(200.0, 200.0, 200.0);
  var material = new MeshBasicMaterial(color:0x0000FF);

  cube = new Mesh(geometry, material);
  scene.add(cube);

  renderer = new WebGLRenderer(canvas:canvas);
}

void animate(num time) {
  window.requestAnimationFrame(animate);

  cube.rotation.x += 0.005;
  cube.rotation.y += 0.01;

  renderer.render(scene, camera);
}

これはwebページに黄色い立方体を回転させながら描くプログラムです。以前はこれはちゃんと動いていたと思うのですが、最近動かしてみると何故か動きませんでした。代わりに次のように表示されます。

Exception: type 'int' is not a subtype of type 'double' of 'value'. (package:three/src/renderers/web_gl_renderer.dart:23)

どうやらThree.dartのコードの内部で例外が発生しているようです。読む限りdouble型の変数にintを入れてしまったのでしょう。C#などと違い、dartはintとdoubleの区別がコード上ではあまりキチンとしていないにもかかわらず、そのくせ実行時にdouble型の変数にintを入れると例外が発生するので、それが理由でしょう。

しかしどうすればいいのでしょう?この例外はThree.dartの内部で生じています。Three.dartのコードを読んでもいいですが、他人の書いたコードを修正するのは少し疲れます。それに、Three.dartのコードをいじらなくてももしかするとちょっとした設定の変更か何かで解決できかもしれません。そう思い、私は数週間を潰してしまいました。しかし結果を考えると、ここは明らかにコードをきちんと読むべきでした。呼び出し側を少し変えるだけで動くようになるからです。

解決法

上のプログラムのWebGLRendererを生成する行を次のように修正してください。

修正前

  renderer = new WebGLRenderer(canvas:canvas);

修正後

  renderer = new WebGLRenderer(canvas:canvas, devicePixelRatio:window.devicePixelRatio.toDouble());

これで動くようになります!

問題が起きた理由

そもそもなぜこんな例外が発生してしまったのかというと、WebGLRendererのdouble型の変数devicePixelRatioにint型が代入されてしまったからです。dartではdouble型の変数にint型のインスタンスを代入すると例外が発生してしまうのですが、コードの時はそれほど厳しくチェックされません。だから次のようなコードを書いていても、見逃してしまったのでしょう。これはWebGLRendererのコンストラクタ中のコードです。

class WebGLRenderer implements Renderer {
..
  double devicePixelRatio;
..

  WebGLRenderer({.., num devicePixelRatio}){
..
    this.devicePixelRatio =
        (devicePixelRatio != null) ? devicePixelRatio : ((window.devicePixelRatio != null) ? window.devicePixelRatio : 1);
..
  }
..
}

numはintでもdoubleでもどちらも入れることができます。しかしintの入ったnumをdoubleに代入したりすると、例外が発生するのです。ここではまさにそれをやっています。

最後の1はint型なので、これがすでにバグなはずです。しかし今回例外が発生したのは、window.devicePixelRatioがintを返したせいです。

window.devicePixelRatioは、numを返すプロパティなので、intを返すかdoubleを返すかわかりません。それをdoubleに代入してしまったので、動かなくなってしまったわけです。(このプロパティは私が動作させて確認した限りでは、intを返すようです。しかし一般にはその保証はないはずです)

コードの残りの部分は見ていませんが、中途半端にnumを使うくらいなら全部numにするべきだったかもしれません。あるいはシンプルにすべてdoubleのほうがすっきりするかもしれませんね。ratioというのはいかにもdoubleっぽい名前ですしね。

拍手[0回]

PR