20211031のJavaに関する記事は8件です。

【AtCoder】JavaでABC224のA,B,C問題を解く!

ABC224のA,B,C問題をJavaで解きました。 備忘録としてまとめておきます。 A問題『Tires』 問題ページ:A - Tires 入力 S : 文字列(末尾は「er」または「ist」、必ず 2文字以上) コード import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String str = sc.next(); //文字列の後方一致チェック if(str.endsWith("r")) { System.out.println("er"); }else { System.out.println("ist"); } } } B問題『Mongeness』 問題ページ:B - Mongeness 入力 縦 $H$ 行、横 $W$ 列のマス目(それぞれ最大 $50$) 上から $i$ 行目、右から $j$ 行目 には整数 $Ai,j$が書かれている コード import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 画面入力値を取得する // 縦行数と横行数を取得する int h = sc.nextInt(); int w = sc.nextInt(); // 各マス目の値を取得する  int[][] a = new int[h][w]; for (int i = 0; i < h; i++) { for(int j = 0; j < w; j++) { a[i][j] = sc.nextInt(); } } for(int i1 = 0; i1 < h; i1++) { for(int i2 = i1 + 1; i2 < h; i2++) { for(int j1 = 0; j1 < w; j1++) { for(int j2 = j1 + 1; j2 < w; j2++) { // 成立しない場合 if(a[i1][j1] + a[i2][j2] > a[i2][j1] + a[i1][j2]) { System.out.println("No"); return; } } } } } System.out.println("Yes"); } } 4重ループですべての $i_1<i_2$ および $j_1<j_2$ を満たす $(i_1,i_2,j_1,j_2)$ の組を全探索して、すべての組が $A_{i_1,j_1}+A_{i_2,J_2}≤A_{i_2,j_1}+A_{i_1,j_2} $ を満たすかを確認する。 C問題『Triangle?』 問題ページ:C - Triangle? 入力 $N$ : 点の数 $Xi$,$Yi$ : 点 $i$ の座標(整数) コード import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 画面入力値を取得する int n = sc.nextInt(); // 座標を取得する int[][] a = new int[n][2]; for(int i = 0; i < n; i++) { a[i][0] = sc.nextInt(); a[i][1] = sc.nextInt(); } int cnt = 0; for(int i = 0; i < n; i++) { for(int j = i +1; j < n; j++) { for(int k = j + 1; k < n; k++) { // 3点が一直線上に並んでいない場合 int x1 = a[i][0], x2 = a[j][0], x3 = a[k][0]; int y1 = a[i][1], y2 = a[j][1], y3 = a[k][1]; if((y2 - y1)*(x3 - x1) != (y3 - y1)*(x2 - x1)) { cnt ++; } } } } System.out.println(cnt); } } 『選ばれた 3 点を線分で結んだ図形が正の面積を持つ三角形になる』 この条件を満たさない場合を考える。 ⇒『3点が一直線上に並んでいる時』面積のある三角形にならない。 選んだ 3 点 $A,B,C$ の座標をそれぞれ $A(x1,y1)$、$B(x2,y2)$、$C(x3,y3)$とすると、 『$3$ 点が一直線上に並んでいる』状態は、線分 $AB$ と $AC$ の傾きが等しい場合である。 数式で表すと $\frac{y_2−y_1}{x_2−x_1}=\frac{y_3−y_1}{x_3−x_1}$ 分母を払って $(y_2−y_1)×(x_3−x_1)=(y_3−y_1)×(x_2−x_1)$ よって、 3重ループで3点の組を全検索して、3点が一直線上に並んでいる場合を除けばよい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apache POIを利用している際にA1形式でセルを取得したい

Excelではセルの位置を示すにあたって、A1形式を利用することが一般的だと思います。Apache POIを利用している際にA1形式でセルを取得したい場合、CellReferenceを利用するとよいです。 たとえば、以下はAB15のセルを取得するサンプルコードです。 CellReference cellReference = new CellReference("AB15"); Row row = sheet.getRow(cellReference.getRow()); if (row != null) { Cell cell = row.getCell(cellReference.getCol()); if (cell != null) { doSomething(cell); } } あるいはCellUtilを利用することもできます。 Cell cell = CellUtil.getCell(CellUtil.getRow(cellReference.getRow(), sheet), cellReference.getCol()); doSomething(cell); 環境情報 (pom.xmlの抜粋) <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.0.0</version> </dependency>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apache POIを使って、シートをソートしたい

Excelにはシートをソートする機能は標準で用意されていません。通常はVBAを利用するのですが、VBAにはソート関数がなく、これを自前を用意するのは面倒。そもそもVBAが苦手ということもあり、JavaとApache POIで処理したことがこの記事を書いたきっかけになります。 前置きはさておき、以下は「src.xlsxのシートをシート名でソートしたものをdst.xlsxに書き出す」というものです。 try (Workbook srcWorkBook = WorkbookFactory.create(Files.newInputStream(Paths.get("src.xlsx"))); OutputStream dstWorkBook = Files.newOutputStream(Paths.get("dst.xlsx"))) { List<String> sheetNames = new ArrayList<>(); for (Sheet sheet : srcWorkBook) { sheetNames.add(sheet.getSheetName()); } Collections.sort(sheetNames); for (int i = 0, len = sheetNames.size(); i < len; i++) { srcWorkBook.setSheetOrder(sheetNames.get(i), i); } srcWorkBook.write(dstWorkBook); } catch (IOException e) { throw new UncheckedIOException(e); } 環境情報 (pom.xmlの抜粋) <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.0.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.0.0</version> </dependency>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kotlinのnullについて

経緯 kotlinはjavaに比べて、随分とエンジニアに親切になったように思います。 個人的に嬉しいのは所謂null安全です。 これがあることで取り扱いがかなり楽になり、NullPointerExceptionなんて久しく見ておりません。 しかし最近になって知った仕様があったので、自戒を込めて基礎的な部分をまとめてみることにしました。 なにがあったか Sample.kt val hoge : Int? = null if (hoge != null) { Log.d("test", "hoge is not null") } else { Log.d("test", "hoge is null") } Log.d("test", "hogeStr is " + hoge.toString()) val fuga = hoge.toString() if (fuga != null) { Log.d("test", "fuga is not null") } else { Log.d("test", "fuga is null") } 上記のようなものがあったとして、 nullの可能性があるものに対してtoString()の時点でそもそもエラーになると思ったのですが そんなことはなく・・・。実行すると以下が出力されました。 2021-10-31 11:33:41.105 7197-7197/com.example.testapplication D/test: hoge is null 2021-10-31 11:33:41.105 7197-7197/com.example.testapplication D/test: hogeStr is null 2021-10-31 11:33:41.105 7197-7197/com.example.testapplication D/test: fuga is not null hogeはnull、hogeStr(hoge.toString()したもの)は"null"という文字列 という結果になりました。kotlinのtoString()は安全、ということです。 ただし、toInt()などはだめです。nullは数値にできませんしね。 (文字列の"null"が返ってくるのは少し嫌な気持ちになりますね・・・。チェックする際は注意しなくてはいけません。) サンプル kotlinのnull周りの仕様について少しだけまとめます。 型宣言 kotlinはnull許容かどうかを明示的に記述する必要があります。 Sample.kt // これはだめ val hoge : String = null // これはOK val fuga : String? = null 「?」がついていない変数はnullでないと言えます。 ただし、プログラム上の不備でnullが入ってくると以下のようなエラーになります。 NullTestSampleKotlin.kt class NullTestSampleKotlin { fun test(str: String) { Log.d("test", str) } } NullTestSample.java public class NullTestSample { public void test() { NullTestSampleKotlin nullTestSampleKotlin = new NullTestSampleKotlin(); nullTestSampleKotlin.test(null); } } Caused by: java.lang.NullPointerException: Parameter specified as non-null is null kotlinでメソッドを定義、引数のStringなどに「?」をつけなかった場合、 kotlinからは引数がnon-nullなことはわかりますが、Javaからはわからずにnullを渡せてしまいます。 Javaからkotlinに移行する際などは注意しましょう。 スマートキャスト nullでないことが確認できていれば、安全に使うことができます。 そこをよしなにやってくれるのがスマートキャストです。 以下の例で、nullチェックをしたブロックの中であればhogeがIntであることは確定しているので、toLong()などを呼び出せます。 ブロック外の呼び出しはコンパイルエラーになります。nullかもしれないですからね。 Sample.kt val hoge : Int? = null if (hoge != null) { // hogeをIntとして扱える val hogeLong = hoge.toLong() } // これはだめ。 val hogeLong2 = hoge.toLong() セーフコール 以下のように「?」に続けて呼び出すことで、 nullでなかった場合のみ呼び出しが成功します。 失敗の場合はnullが返却されます。 Sample.kt val hoge : Int? = null // nullが返ってくる可能性があるので、hogeLongの型は「Long?」になります。 val hogeLong = hoge?.toLong() めちゃくちゃ使います。 エルヴィス演算子 「?:」を用いることにより、左辺がnullだった場合に右辺を使用してくれます。 エルヴィス演算子が連続することもありますが、少しわかりづらくなりますね。 Sample.kt // hogeがnullでない場合toLong()の結果、nullだった場合は-1LがhogeLongに代入されます。 // したがってhogeLongの型は「Long」に確定しています。 val hogeLong = hoge?.toLong() ?: -1L あのロックな方に見えることからその名前がついているそうです。 終わりに Javaよりもチェックが厳格になったことにより、記述するのは少しだけ面倒になりましたが 品質は良くなりやすいのかなあと思います。 たまにJavaのコードを書く際に混乱してしまうので、注意してコードを書いていきたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JDBC・Spring JDBC / JPA・Spring Data・Spring Data JPA・Hibernateの関係性

導入 ・今までの業務でなんとなく使っていたSpring JDBC、Spring Data JPAらへんの理解があいまいだったので記事にまとめる。 まず結論 ・JDBC...JavaでDBに接続するためのAPI。ResultSetからデータ抜き出すのだるいやつ。 ・Spring JDBC...JDBCの使いづらいところを取っ払ってくれたもの。JDBCより断然こっちの方がいい。application.propertiesにDB接続情報定義するだけで接続できるのはありがたい。 ・JPA...Java標準のORMの仕様。JDBC系と異なりSQLを自動生成してくれたり、ORMでデータ取得結果をEntityクラスにぶちこんでくれる。 ・Spring Data...色んなDBを使いやすくする複数のフレームワークの親玉。 ・Spring Data JPA...JPAを使いやすくする抽象クラス/インタフェースを提供してくれる。これはJPAの実装ではない。 ✅ JavaでORMの恩恵(Entity間の紐付け等)を存分に授かりたいなら「JPA」を使う。さらにJPAを簡単に使いたいなら「Spring Data JPA」を使う。 ✅ SQLをできるだけナチュラルな状態で扱いたいなら「Spring JDBC」を使う。Spring JDBCでもORMっぽいことはできるが、Entity間の紐付け(LAZY FETCH等)はできない。 JDBC ・JavaでDBに接続するためのAPI ・ResultSetでデータを受け取り、Beanクラスにデータ格納する手間があるやつ Spring JDBC ・JDBCをもっといいかんじに使えるようにしたフレームワーク ■ 普通のJDBCとの違い ・JdbcTemplateクラスのメソッドを使うだけでデータ登録/更新/検索/削除が可能。 ・RowMapperインターフェースを使えばBeanクラスへのマッピング処理をいい感じにできる。 ・application.propertiesにDB接続情報を定義するだけで接続可能(厳密にはこれSpringBootの機能かな # DB接続情報を定義するだけでOK spring.datasource.url spring.datasource.username spring.datasource.password spring.datasource.driver-class-name spring.datasource.initilizationmode spring.datasource.schema spring.datasource.data JPA ・Java標準のORMの仕様(specification) ・実装(implementation)が別で提供されている。Springはデフォルトで「Hibernate」を実装として利用する。 https://stackoverflow.com/questions/41902856/spring-boot-jpa-hibernate-as-default ・ORMを実現する仕組みとして「Entity」「EntityManager」の2つの理解が欠かせない。   Entity...メモリ上のJavaオブジェクト   EntityManager...EntityーーーDB を紐づける仲介者   EntityーーーEntityManager(Persistence Context)ーーーDB EntityManagerにはPersistence Contextと呼ばれる、Entityを管理するための領域があり、データベースアクセスをする場合はPersistence Context内のEntityを取得したり、新規でEntityを登録する必要があります。 上記によって、EntityManagerがEntityの状態を追うことができ、適切なタイミングでデータベースと同期を取ることができます。 EntityManagerにはEntityの状態を変更したり、データベースと同期をとるAPIが用意されています。 ・EntityManagerに対する操作したタイミングでデータベースには反映されず、トランザクションがコミットもしくは強制的に同期(flush)されたタイミングでPersistence Contextに蓄積したEntityへの変更がデータベースへ反映される。 ・Persistence Contextはトランザクション単位 ・Entity間の紐づけ =@OneToOne等をつけることで関連エンティティを取得できる(LAZY/EAGER) ・Lazy Loading(必要になるまで取得しない)  ...意識しないとクエリ実行タイミングがトランザクション外となりエラーになることもある(´・ω・) Spring Data ・数ある「Spring Data ---」フレームワークの親玉。 ・JPAをもっと使いやすくする「Spring Data JPA」に始まり、DB関連の便利なフレームワークが沢山ある This is an umbrella project which contains many subprojects that are specific to a given database. https://spring.io/projects/spring-data Spring Data JPA ・JPAを使いやすくしてくれる抽象クラス/インタフェース群を提供 ・これはJPAの実装ではない。あくまで"JPAという仕様を使いやすくしたもの"である。 Spring Data JPAを使わない場合、EntityManagerを直接操作する必要がある。 Spring Data JPAを使う場合、フレームワーク側で用意されているメソッド(findByXxx・save・saveAll等)を呼び出すだけでDB操作が可能。 ・下記のようにインタフェース(CrudRepository)を継承するだけでいい。  自分でJPQLを作成したければ@Queryを使えばよい。 import org.springframework.streotype.Repository; import org.springframework.data.repository.CrudRepository; @Repository public inteface HogeRepository extends CrudRepository<HogeEntity, String> {  // 自分でクエリを定義する  @Query(value = "SELECT DISTINCT hoge from HogeEntity hoge WHERE ~~~~ ");   List<HogeEntity> findHogeBy~~~(@Param("fuga") String fuga); } JDBC(Spring JDBC)とJPA(Spring Data JPA)の比較 ・大きな違いは「SQLを自分で書くかどうか」  JDBC:自分でNativeなSQLを書く。  JPA:SQLを自動生成してくれる。またJPAだけで使えるクエリ言語「JPQL」を使える。 JPQL...普通のSQLと違うところは「Entity間の紐付けをいい感じにやってくれる」ところ  JPA(Hibernate)で勝手にSQL作って発行してくれるのは便利だしコーディングがすぐ終わる良さもある。  しかし SQLが実行されるタイミングが分かりづらかったり裏側で何が起こっているのか見えづらい  ので、何をとるかでJDBC・JPAを採択するかは変わってくるだろう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HS256のJWS署名検証テスト

環境 Version Java 11 Spring Boot 2.5.6 JUnit 5.7.2 HS256の署名検証 仕様 OIDC 1.0 IDTokenValidation HS256のID Tokenは、client_secretを鍵として用いて署名されている。 やり方 ここ(JSON Web Signature (JWS) Example JWS using HMAC SHA-256)をお手本にするのが良さそう。 コード @Slf4j @SpringBootTest public class JWTSignatureVerifyTest { // TODO 調達したsecretで置き換えてください。 @Value("${spring.security.oauth2.client.registration.dummy.client-secret}") private String secret; @Test public void test() throws NoSuchAlgorithmException, InvalidKeyException { // TODO 調達したHS256署名されたIDトークンで置き換えてください String idToken = "dummy"; var signatureStart = idToken.lastIndexOf(".") + 1; var bodyEnd = idToken.lastIndexOf("."); var headAndBody = idToken.substring(0, bodyEnd); var signature = idToken.substring(signatureStart); var jwtInput = headAndBody.getBytes(StandardCharsets.UTF_8); String algo = "HmacSHA256"; SecretKeySpec sk = new SecretKeySpec(secret.getBytes(), algo); Mac mac = Mac.getInstance(algo); mac.init(sk); log.info("headAndBody:{}", headAndBody); byte[] mac_bytes = mac.doFinal(jwtInput); byte[] encoded = Base64.getEncoder().encode(mac_bytes); log.info("original signature:\t{}", signature); var encodedString = new String(encoded); log.info("encodedString:\t{}", encodedString); var encodedStringReplaced = encodedString.replaceAll("\\+", "-").replace("/", "_").replaceAll("="); log.info("encodedString replaced:\t{}", encodedStringReplaced); Assertions.assertThat(encodedStringReplaced).isEqualTo(signature); } } 最後の方の.replaceAll("\\+", "-").replace("/", "_")について rfc7515 appendig-Cに詳しくある。 最後の方のreplaceAll("=","")について rfc7515 appendig-Cに詳しくある。 Base64エンコーディングすると、規定長にするために=がパディングされる。 参考資料 JavaでHMAC-SHA256を計算する JSON Web Signature (JWS) Example JWS using HMAC SHA-256 Auth0 validate-json-web-tokens oauth2login-advanced-idtoken-verify JSON Web Token (JWT) Algorithms
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EJB・EJB3・JPAの関係性

導入 hibernate-toolsでリバースエンジニアリングする際に「EJB3」「JPA」「POJO」らへんの単語がでてきたが、関係性がちゃんと理解できなかったので記事にまとめる。 まず結論(EJB・EJB3・JPAの関係性) ・EJB3は「EJBの上位互換」 ・EJBでは「POJOを使えない」 EJB3から「POJOを使える」 ・JPAは「EJB3でPOJOを使うために利用されているもの」。JPA単体では1つの技術として独立しており、EJB3がJPAという技術に依存している関係性 JPA  「JavaでORMを使うための仕様」 ・Java Persistence API ・Java標準のORMの仕様(specification) ・実装(implementation)が別で提供されている。Springはデフォルト実装として「Hibernate」を利用する。 https://stackoverflow.com/questions/41902856/spring-boot-jpa-hibernate-as-default ・Beanクラスにある下記のようなアノテーションがJPAで定義されているもの import javax.persistence.Entity; @Entity public class SampleBean {       public String field1;       public int field2; } Hibernate  「JPA(仕様)の実装」 ・沼なのでここでは詳しい説明割愛 ・JPAの実装ということだけ理解しておけばとりあえずいい。 EJB(Enterprise Java Beans)  「エンタープライズアプリケーション用の仕組み/フレームワーク」   もしくは  「EJBコンテナに登録するオブジェクト(POJOと対極にあるもの)」   を指す  「POJOが使えない(=EJBを利用する)フレームワーク」 ■ EJBとはどんな仕組み? EJBコンテナと呼ばれるところにオブジェクトをプールし、そのコンテナからオブジェクトを取り出して物理的に離れたところにあるオブジェクトにアクセスできるようにする仕組み http://lasaya.blog129.fc2.com/blog-entry-17.html ・このフレームワークを使うにあたり、オブジェクトは一定の決まりに従って実装する必要がある(フレームワーク側で用意されたインタフェースをimplementsする必要があったりなど) ==こういう「フレームワークに縛られた実装/コーディング」をする必要がないものが「Plain(単純な) Old Java Object(POJO)」と呼ばれる。 EJB3  「POJOを使えるEJB」 ・EJB2までは「javax.ejb.EntityBeanインタフェースをimplmentsしたBeanクラス」のようにフレームワークに縛られた状態にあった。 ・「Bean1つ定義するたびに毎回javax.ejb.EntityBeanインタフェースに沿って実装するのだるくね?」という流れでEJB3ではPOJOとして扱えるようにアップデートされた。  =EJB2まで=   javax.ejb.EntityBeanインタフェースをimplments  =EJB3=   JPAの@Entity(javax.persistence.entity)等のアノテーションをつけるだけでOK まとめ(EJB・EJB3・JPAの関係性) ・EJB3は「EJBの上位互換」 ・EJBでは「POJOを使えない」 EJB3では「POJOを使える」 ・JPAは「EJB3でPOJOを使うために利用されているもの」。JPA単体では1つの技術として独立しており、EJB3がJPAという技術に依存している関係性 自分用MEMO ・hibernate-toolsのreverse engineeringでEntityクラスを自動生成する時に「ejb3」というオプションがある。この意味は「(ejb3で利用している)JPA・Hibernateのアノテーション等を利用してEntityクラスを生成するかどうか」である。Spring Data JPA等のJPAを利用するアプリケーションの場合はこいつを「true」に設定する必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Processingを利用して魚群再現をする(1)

魚群再現事始め 近年盛んになりつつあるProcessingを用いたジェネラティブアートの世界ですが、 某アニメを視聴して魚群の動きを再現することが可能かを検証するべく、実際に実装してみることにしました。実行環境についてはProcessing3.0を利用していきます。 群体シミュレーションについて 群体シミュレーションをする場合であれば、Boidsアルゴリズムを利用することが一般的で、Boidsアルゴリズムの実装についてはhttps://qiita.com/odanny/items/e0c0a00e13c2b4839cec の記事を参考にしてもらうのが良いと思います。 簡単に説明すると、各オブジェクト間で 分離 (Separation) 整列 (Alignment) 結合 (Cohesion) の三種類の力を働かせることになります。 本実装では、3種類の力に加えて、壁に対して拒絶間(斥力)が働くように実装していきます。 Fishクラスの実装 まずは、魚のクラスを実装していきます。 各変数はprivateにすることで外部からアクセスすることを防ぎます。 各変数についてはposが位置、velocityが速度、accelが加速度になります。 また、変数は現時点で魚のイメージの代わりに円を利用しているので、dは直径として利用する変数になります。 class Fish { private PVector pos; private PVector velocity; private PVector accel; private float d; private float radius; private float COEF_0, COEF_1, COEF_2, COEF_3; Fish(float xpos, float ypos, float xsp, float ysp, float xac, float yac, float diameter) { pos=new PVector(xpos, ypos); velocity=new PVector(xsp, ysp); accel=new PVector(xac, yac); d=diameter; } これからclassのメンバ関数を実装するのでこの時点で括弧は閉じません。 それでは、オブジェクト固有の関数を実装していきます。 目視できる範囲の設定 魚オブジェクトの捜索 魚オブジェクトの目視範囲の設定を行います。 キャンバス全体を目視範囲とすると、対象となる他のオブジェクトや壁の量があまりに大きくなり、例外が画面外に出てしまった場合には魚たちが画面外に飛んで行ってしまいます。 まずは、目視範囲の魚オブジェクトの捜索から始めます。 private Fish[] SearchBoid(Fish[] fishes, float radius) { ArrayList<Fish> boid=new ArrayList<Fish>(); PVector diff; for (int i=0; i<fishes.length; i++) { diff=PVector.sub(fishes[i].pos, pos); if (diff.mag()<radius && diff.mag()!=0) { boid.add(fishes[i]); } } Fish [] rslt=boid.toArray(new Fish[boid.size()]); return rslt; } オブジェクトは配列として参照渡しを行います。 設定した半径の中に存在する魚オブジェクトをArrayListとして定義し、配列に変換したものを返します。 Rule0(壁に対する斥力) まず、壁に対する斥力を設定します。 壁を有無をintの二次元配列Fieldとして参照渡しを行い、 目視範囲内に壁が存在する場合、斥力を感じるように設定していきます。 private PVector Rule0(int[][] Field, float radius) { PVector direction=new PVector(0, 0); for (int i=int(pos.x-radius); i<int(pos.x+radius); i++) { for (int j=int(pos.y-radius); j<int(pos.y+radius); j++) { //半径範囲内である場合 if (sqrt(pow(pos.x-i, 2)+pow(pos.y-j, 2))<radius) { //境界範囲外の場合 if ((i<0)||(Width<=i) || (j<0 || Height<=j)) { PVector diff=new PVector(pos.x-i, pos.y-j); direction=PVector.add(direction, PVector.div(diff, pow(diff.mag(), 2))); } //壁の存在を感知した場合 else if (Field[i][j]==1) { PVector diff=new PVector(pos.x-i, pos.y-j); direction=PVector.add(direction, PVector.div(diff, pow(diff.mag(), 2))); } } } } return direction; } 本コードでは距離に対して二乗の大きさの斥力を加えます。 これは上記の記事のコードを踏襲したものであるため、魚同士の分離にも適用しています。 Rule1(分離) 周囲の魚オブジェクトに対する斥力を適用していきます。 斥力の大きさはRule0と同様に距離に対する二乗の力が発生するように設定します。 private PVector Rule1(Fish[] fishes) { PVector A=new PVector(0, 0); if (fishes.length!=0) { for (int i=0; i<fishes.length; i++) { //他の魚オブジェクトからの距離 PVector diff = PVector.sub(pos, fishes[i].pos); if (diff.mag()!=0) { A = PVector.add(A, PVector.div(diff, pow(diff.mag(), 2))); } } A = PVector.div(A, fishes.length); } return A; } Rule2(整列) 視界の範囲内に存在する魚群の平均速度に合わせるように力が働きます。 この時、PVectorは2次元のベクトルなので、平均の速度から自分の速度を引きます。 private PVector Rule2(Fish[] fishes) { PVector B = new PVector(0, 0); PVector rslt=new PVector(0, 0); if (fishes.length!=0) { for (int i=0; i<fishes.length; i++) { B = PVector.add(B, fishes[i].velocity); } B = PVector.div(B, (fishes.length)); rslt=PVector.sub(B, velocity); } return rslt; } Rule3(結合) 自分の視界の中の魚群の平均の位置に引き寄せられるように力が働きます。 Rule2と同様に自らの位置を引きます。 private PVector Rule3(Fish[] fishes) { PVector C=new PVector(0, 0); PVector rslt=new PVector(0, 0); if (fishes.length!=0) { for (int i=0; i<fishes.length; i++) { C=PVector.add(C, fishes[i].pos); } C=PVector.div(C, fishes.length); rslt=PVector.sub(C, pos); } return rslt; } update関数の実装 publicで定義したupdate関数によって、Rule0からRule3を実行し、設定した係数をかけた合計を加速度とします。加速度を求めたうえで速度の更新、位置の更新を行います。 また、velocityを一定以上までになると、その範囲内に収まるように調整します。理由としては、速度が大きすぎると、画面外に飛び出してしまい魚が返ってこなくなることがあるためです。 public void update(Fish[] fishes) { COEF_0=0.1; COEF_1=5; COEF_2=0.7; COEF_3=0.017; radius=20; Fish[] boids=SearchBoid(fishes, radius); PVector NUM0=PVector.mult(Rule0(Field, radius), COEF_0); PVector NUM1=PVector.mult(Rule1(boids), COEF_1); PVector NUM2=PVector.mult(Rule2(boids), COEF_2); PVector NUM3=PVector.mult(Rule3(boids), COEF_3); accel=PVector.add(PVector.add(NUM0, NUM1), PVector.add(NUM2, NUM3)); velocity=PVector.add(velocity, accel); if (10<velocity.mag()) { velocity=PVector.mult(velocity.normalize(), 10); } pos=PVector.add(pos, velocity); } その他の関数 クラス内部に動作をサポートする関数を作成します。 RandomActive関数は動作の複雑性を増すためにランダムなベクトルを速度に足し合わせます。また、魚を表示するために専用の関数を設定した関数がShowFish関数になります。 public void RandomActive() { PVector rand_vel=new PVector(0.1*randomGaussian(), 0.1*randomGaussian()); velocity=PVector.add(velocity, rand_vel); pos=PVector.add(pos, velocity); } public void ShowFish() { ellipse(pos.x, pos.y, d, d); } プログラム本体の実装 次に、クラス外部のプログラムをまとめて示します。 全てのFishオブジェクトの配列をfishes、場所の定義をint型の配列Fieldで実現します。また、Fieldの定義に対して、画面上の表示はdraw関数内部でrect関数を利用して、直接書き込んでいますが全体の走査で自動化することも可能です。 int Width=1000; int Height=1000; int FishNum=500; Fish [] fishes = new Fish[FishNum]; int [][] Field= new int[Width][]; void setField(int x_min, int y_min, int x_len, int y_len) { for (int i=x_min; i<x_min+x_len; i++) { for (int j=y_min; j<y_min+y_len; j++) { Field[i][j]=1; } } } void settings() { size(Width, Height); } void setup() { background(0, 0, 0); fill(255, 255, 255); noStroke(); for (int i=0; i<FishNum; i++) { fishes[i]=new Fish(random(30, Width-30), random(30, Height-30), random(3), random(3), 0, 0, 5); } //場の設定 for (int i=0; i<Width; i++) { Field[i]=new int[Height]; for (int j=0; j<Height; j++) { if (i<30 || Width-30<i) { Field[i][j]=1; } else if (j<30 || Height<j) { Field[i][j]=1; } else { Field[i][j]=0; } } } setField(350, 400, 250, 200); } void draw() { background(0, 0, 0); fill(0, 0, 0); //Field fill(255, 0, 0); rect(350, 400, 250, 200); rect(0, 0, 30, Height); rect(0, 0, Width, 30); rect(Width-30, 0, 30, Height); rect(0, Height-30, Width, 30); fill(255, 255, 255); for (int i=0; i<FishNum; i++) { fishes[i].ShowFish(); } for (int i=0; i<FishNum; i++) { fishes[i].update(fishes); fishes[i].RandomActive(); } } 実行した場合の動作 以下に、実際に動作させたときの感じを示します。若干動作が変わっている可能性がありますが、大体動作はこんな感じになると思います。 これからの実装について 現時点で意思を持って動いているようには見えていても、魚が泳いでいるようには見えません。 原因としては壁や魚同士の斥力の設定(壁や魚に衝突しているように見える)や各係数の設定が原因であると考えられます。そのため、(2)以降は以下のようなことをやっていく予定です。 カメラを利用したFieldの設定 (動作速度の向上) 魚群のアニメーション作成 パラメータの調整 (2)の記事についてはこちら(2021年11月頃更新予定)。 注意 開発を進めながら投稿しているため、特定時点のプログラムを再現したものを掲載しています。そのため、そのままコピペしても動作しない可能性があります。その場合、都度修正して実行してみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む