diff --git a/apps/typegpu-docs/public/assets/genetic-car/car-hatchback-blue.obj b/apps/typegpu-docs/public/assets/genetic-car/car-hatchback-blue.obj new file mode 100644 index 0000000000..dfdc3dc755 --- /dev/null +++ b/apps/typegpu-docs/public/assets/genetic-car/car-hatchback-blue.obj @@ -0,0 +1,2505 @@ +# Blender v2.71 (sub 0) OBJ File: 'export_setup.blend' +# www.blender.org +mtllib car-hatchback-blue.mtl +o car-hatchback-blue_Cube.031 +v 0.316150 0.558498 -0.776764 +v 0.269927 0.558498 -0.776764 +v 0.245550 0.540208 -0.778593 +v 0.245590 0.540208 -0.757994 +v 0.316182 0.485337 -0.757995 +v 0.316150 0.485337 -0.784080 +v 0.269959 0.485337 -0.757995 +v 0.269927 0.485337 -0.784080 +v 0.245590 0.503627 -0.757994 +v 0.245550 0.503627 -0.782251 +v 0.269959 0.558498 -0.757995 +v 0.316182 0.558498 -0.757995 +v 0.351226 0.424577 0.969229 +v 0.351226 0.424554 0.972532 +v 0.339256 0.419618 0.969229 +v 0.339256 0.419596 0.973149 +v 0.334297 0.407648 0.969229 +v 0.334297 0.407625 0.974639 +v 0.339256 0.395677 0.969229 +v 0.339256 0.395655 0.976130 +v 0.351226 0.390719 0.969229 +v 0.351226 0.390696 0.976747 +v 0.363197 0.395677 0.969229 +v 0.363197 0.395655 0.976129 +v 0.368155 0.407648 0.969229 +v 0.368155 0.407625 0.974639 +v 0.363197 0.419618 0.969229 +v 0.363197 0.419596 0.973149 +v 0.277127 0.335937 -0.266135 +v 0.277127 0.381259 -0.333963 +v 0.408593 0.188100 -0.807807 +v 0.408593 0.590666 -0.769750 +v 0.344886 0.823475 -0.143262 +v 0.344887 0.823475 0.165251 +v 0.277126 0.397173 -0.413972 +v 0.277126 0.381259 -0.493981 +v 0.408597 0.432986 0.968164 +v 0.408594 0.568670 -0.220099 +v 0.408596 0.541334 0.462025 +v 0.344885 0.823475 -0.351272 +v 0.408597 0.282150 0.995399 +v 0.408594 0.281678 -0.188056 +v 0.446216 0.188100 0.478124 +v 0.446216 0.260083 0.492442 +v 0.446216 0.321107 0.533217 +v 0.446216 0.361882 0.594241 +v 0.446216 0.376200 0.666224 +v 0.446216 0.361882 0.738207 +v 0.446216 0.321107 0.799231 +v 0.446216 0.260083 0.840006 +v 0.446216 0.188100 0.854324 +v 0.446216 0.116117 0.840006 +v 0.446216 0.055093 0.799231 +v 0.446216 0.014318 0.738207 +v 0.446216 0.000000 0.666224 +v 0.446216 0.014318 0.594241 +v 0.446216 0.055093 0.533217 +v 0.446216 0.116117 0.492442 +v 0.446214 0.188100 -0.291707 +v 0.446213 0.116117 -0.587754 +v 0.446213 0.055093 -0.546979 +v 0.446214 0.014318 -0.485955 +v 0.446214 0.000000 -0.413972 +v 0.446214 0.014318 -0.341990 +v 0.446214 0.055093 -0.280966 +v 0.446214 0.116117 -0.240191 +v 0.446214 0.188100 -0.225873 +v 0.446214 0.260083 -0.240191 +v 0.446214 0.321107 -0.280966 +v 0.446214 0.361882 -0.341990 +v 0.446214 0.376200 -0.413972 +v 0.446214 0.361882 -0.485955 +v 0.446213 0.321107 -0.546979 +v 0.446213 0.260083 -0.587754 +v 0.446216 0.188100 0.788489 +v 0.446216 0.234889 0.779182 +v 0.446216 0.274555 0.752678 +v 0.446216 0.301058 0.713013 +v 0.446216 0.310365 0.666224 +v 0.446216 0.301058 0.619435 +v 0.446216 0.274555 0.579769 +v 0.446216 0.234889 0.553266 +v 0.446216 0.188100 0.543959 +v 0.277126 0.335937 -0.561809 +v 0.408596 0.188100 0.421694 +v 0.277126 0.268109 -0.607130 +v 0.408594 0.188100 -0.169442 +v 0.408597 0.188100 0.995399 +v 0.277126 0.188100 -0.623045 +v 0.431824 0.188100 -0.623046 +v 0.408596 0.281678 0.440308 +v 0.408596 0.361009 0.493315 +v 0.408594 0.361009 -0.241064 +v 0.408593 0.188100 -0.658502 +v 0.408593 0.281678 -0.639889 +v 0.408593 0.361009 -0.586881 +v 0.408594 0.414016 -0.507550 +v 0.408594 0.432630 -0.413972 +v 0.408594 0.414016 -0.320395 +v 0.431824 0.268109 -0.607131 +v 0.431824 0.335937 -0.561809 +v 0.431824 0.381259 -0.493981 +v 0.431824 0.397173 -0.413972 +v 0.433047 0.188100 -0.193895 +v 0.433047 0.272320 -0.210648 +v 0.433047 0.343718 -0.258354 +v 0.433047 0.391425 -0.329753 +v 0.433047 0.408177 -0.413972 +v 0.433047 0.391425 -0.498192 +v 0.433046 0.343718 -0.569590 +v 0.433046 0.272320 -0.617297 +v 0.433046 0.188100 -0.634049 +v 0.431824 0.381259 -0.333964 +v 0.431824 0.335937 -0.266135 +v 0.431825 0.268109 -0.220814 +v 0.431825 0.188100 -0.204899 +v 0.295736 0.055093 0.533217 +v 0.446216 0.141311 0.553266 +v 0.446216 0.101646 0.579769 +v 0.446216 0.075142 0.619435 +v 0.446216 0.065835 0.666224 +v 0.446216 0.075142 0.713013 +v 0.446216 0.101646 0.752678 +v 0.446216 0.141311 0.779182 +v 0.277127 0.268109 -0.220814 +v 0.277127 0.188100 -0.204899 +v 0.276924 0.188100 -0.413972 +v 0.431826 0.188100 0.457151 +v 0.277128 0.188100 0.457151 +v 0.277128 0.268109 0.473066 +v 0.277128 0.335937 0.518387 +v 0.277129 0.381259 0.586215 +v 0.277129 0.397173 0.666224 +v 0.277129 0.381259 0.746233 +v 0.277129 0.335937 0.814061 +v 0.408596 0.414016 0.572646 +v 0.408596 0.432630 0.666224 +v 0.408596 0.414016 0.759802 +v 0.408596 0.361009 0.839133 +v 0.408596 0.281678 0.892140 +v 0.408596 0.188100 0.910754 +v 0.431826 0.268109 0.473065 +v 0.431826 0.335937 0.518387 +v 0.431826 0.381259 0.586215 +v 0.431826 0.397173 0.666224 +v 0.433049 0.188100 0.886301 +v 0.433049 0.272320 0.869549 +v 0.433049 0.343718 0.821842 +v 0.433049 0.391425 0.750444 +v 0.433049 0.408177 0.666224 +v 0.433049 0.391425 0.582004 +v 0.433049 0.343718 0.510606 +v 0.433049 0.272320 0.462899 +v 0.433049 0.188100 0.446147 +v 0.431827 0.381259 0.746233 +v 0.431827 0.335937 0.814061 +v 0.431827 0.268109 0.859382 +v 0.431827 0.188100 0.875297 +v 0.277129 0.268109 0.859383 +v 0.277129 0.188100 0.875297 +v 0.276926 0.188100 0.666224 +v 0.295736 0.014318 0.594241 +v 0.295736 0.000000 0.666224 +v 0.295736 0.014318 0.738207 +v 0.295736 0.055093 0.799231 +v 0.295736 0.116117 0.840006 +v 0.295736 0.188100 0.854324 +v 0.295736 0.260083 0.840006 +v 0.295736 0.321107 0.799231 +v 0.295736 0.361882 0.738207 +v 0.295736 0.376200 0.666224 +v 0.295736 0.361882 0.594241 +v 0.295736 0.321107 0.533217 +v 0.295736 0.260083 0.492442 +v 0.295736 0.188100 0.478124 +v 0.295736 0.116117 0.492442 +v 0.295736 0.188100 0.788489 +v 0.295736 0.234889 0.779182 +v 0.295736 0.274555 0.752679 +v 0.295736 0.301058 0.713013 +v 0.295736 0.310365 0.666224 +v 0.295736 0.301058 0.619435 +v 0.295736 0.274555 0.579770 +v 0.295736 0.234889 0.553266 +v 0.295736 0.188100 0.543959 +v 0.446213 0.188100 -0.602072 +v 0.295736 0.141311 0.553266 +v 0.295736 0.101646 0.579770 +v 0.295736 0.075142 0.619435 +v 0.295736 0.065835 0.666224 +v 0.295736 0.075142 0.713013 +v 0.295736 0.101646 0.752679 +v 0.295736 0.141311 0.779182 +v 0.446214 0.234889 -0.301014 +v 0.446214 0.274555 -0.327518 +v 0.446214 0.301058 -0.367184 +v 0.446214 0.310365 -0.413972 +v 0.446214 0.301058 -0.460761 +v 0.446214 0.274555 -0.500427 +v 0.446213 0.234889 -0.526931 +v 0.446213 0.188100 -0.536238 +v 0.295733 0.055093 -0.546979 +v 0.446213 0.141311 -0.526931 +v 0.446214 0.101646 -0.500427 +v 0.446214 0.075142 -0.460761 +v 0.446214 0.065835 -0.413972 +v 0.446214 0.075142 -0.367184 +v 0.446214 0.101646 -0.327518 +v 0.446214 0.141311 -0.301014 +v 0.295734 0.014318 -0.485955 +v 0.295734 0.000000 -0.413972 +v 0.295734 0.014318 -0.341989 +v 0.295734 0.055093 -0.280965 +v 0.295734 0.116117 -0.240190 +v 0.295734 0.188100 -0.225872 +v 0.295734 0.260083 -0.240191 +v 0.295734 0.321107 -0.280965 +v 0.295734 0.361882 -0.341989 +v 0.295734 0.376200 -0.413972 +v 0.295734 0.361882 -0.485955 +v 0.295733 0.321107 -0.546979 +v 0.295733 0.260083 -0.587754 +v 0.295733 0.188100 -0.602072 +v 0.295733 0.116117 -0.587754 +v 0.295734 0.188100 -0.291707 +v 0.295734 0.234889 -0.301014 +v 0.295734 0.274555 -0.327518 +v 0.295734 0.301058 -0.367183 +v 0.295734 0.310365 -0.413972 +v 0.295734 0.301058 -0.460761 +v 0.295734 0.274555 -0.500427 +v 0.295733 0.234889 -0.526930 +v 0.295733 0.188100 -0.536237 +v 0.295733 0.141311 -0.526930 +v 0.295734 0.101646 -0.500427 +v 0.295734 0.075142 -0.460761 +v 0.295734 0.065835 -0.413972 +v 0.295734 0.075142 -0.367183 +v 0.295734 0.101646 -0.327518 +v 0.295734 0.141311 -0.301014 +v 0.436809 0.188100 -0.303934 +v 0.436811 0.188100 0.776262 +v 0.436811 0.230210 0.767886 +v 0.436811 0.265909 0.744033 +v 0.436811 0.289762 0.708334 +v 0.436811 0.298139 0.666224 +v 0.436811 0.289762 0.624114 +v 0.436811 0.265909 0.588415 +v 0.436811 0.230210 0.564562 +v 0.436811 0.188100 0.556185 +v 0.436811 0.188100 0.666224 +v 0.436811 0.145990 0.564562 +v 0.436811 0.110291 0.588415 +v 0.436811 0.086438 0.624114 +v 0.436811 0.078062 0.666224 +v 0.436811 0.086438 0.708334 +v 0.436811 0.110291 0.744033 +v 0.436811 0.145990 0.767886 +v 0.436809 0.230210 -0.312310 +v 0.436809 0.265909 -0.336163 +v 0.436809 0.289762 -0.371863 +v 0.436809 0.298139 -0.413972 +v 0.436809 0.289762 -0.456082 +v 0.436809 0.265909 -0.491781 +v 0.436809 0.230210 -0.515635 +v 0.436809 0.188100 -0.524011 +v 0.436809 0.188100 -0.413972 +v 0.436809 0.145990 -0.515635 +v 0.436809 0.110291 -0.491781 +v 0.436809 0.086438 -0.456082 +v 0.436809 0.078062 -0.413972 +v 0.436809 0.086438 -0.371863 +v 0.436809 0.110291 -0.336163 +v 0.436809 0.145990 -0.312310 +v 0.268462 0.411383 0.969006 +v 0.268462 0.411308 0.983974 +v 0.308364 0.394855 0.969006 +v 0.308364 0.394780 0.985993 +v 0.324892 0.354953 0.969006 +v 0.324892 0.354878 0.990865 +v 0.308364 0.315051 0.969006 +v 0.308364 0.314976 0.995738 +v 0.268462 0.298523 0.969006 +v 0.268462 0.298448 0.997757 +v 0.228560 0.315051 0.969006 +v 0.228560 0.314976 0.995738 +v 0.212032 0.354953 0.969007 +v 0.212032 0.354878 0.990866 +v 0.228560 0.394855 0.969007 +v 0.228560 0.394780 0.985993 +v 0.168769 0.411348 0.971757 +v 0.168769 0.298461 0.985679 +v 0.168769 0.411348 0.983994 +v 0.168769 0.298461 0.997916 +v 0.362509 0.590666 -0.769750 +v 0.362511 0.541334 0.462025 +v 0.352164 0.785855 -0.169238 +v 0.306079 0.785855 -0.418785 +v 0.306081 0.785855 0.204021 +v 0.407745 0.546528 0.414199 +v 0.352165 0.785855 0.155219 +v 0.408594 0.567189 -0.177693 +v 0.352165 0.785855 -0.126806 +v 0.408594 0.578782 -0.429418 +v 0.352164 0.785855 -0.241547 +v 0.380379 0.568670 -0.220099 +v 0.323949 0.785855 -0.169238 +v 0.379530 0.546528 0.414199 +v 0.323950 0.785855 0.155219 +v 0.380379 0.567189 -0.177693 +v 0.323950 0.785855 -0.126806 +v 0.380379 0.578782 -0.429418 +v 0.323949 0.785855 -0.241547 +v 0.362509 0.585756 -0.618210 +v 0.331380 0.558498 -0.776764 +v 0.331411 0.558498 -0.757995 +v 0.331380 0.485337 -0.784080 +v 0.331411 0.485337 -0.757995 +v 0.348302 0.485337 -0.784080 +v 0.348334 0.485337 -0.757995 +v 0.372671 0.503627 -0.782251 +v 0.372711 0.503627 -0.757995 +v 0.348302 0.558498 -0.776764 +v 0.372671 0.540208 -0.778593 +v 0.348334 0.558498 -0.757995 +v 0.372711 0.540208 -0.757995 +v -0.316154 0.558498 -0.776763 +v -0.269931 0.558498 -0.776763 +v -0.245553 0.540208 -0.778592 +v -0.245594 0.540208 -0.757993 +v -0.316186 0.485337 -0.757993 +v -0.316154 0.485337 -0.784079 +v -0.269962 0.485337 -0.757994 +v -0.269931 0.485337 -0.784079 +v -0.245594 0.503627 -0.757993 +v -0.245553 0.503627 -0.782250 +v -0.269962 0.558498 -0.757994 +v -0.316186 0.558498 -0.757993 +v -0.351223 0.424577 0.969231 +v -0.351223 0.424554 0.972533 +v -0.339252 0.419618 0.969230 +v -0.339252 0.419596 0.973150 +v -0.334294 0.407648 0.969230 +v -0.334294 0.407625 0.974641 +v -0.339252 0.395677 0.969230 +v -0.339252 0.395655 0.976131 +v -0.351223 0.390719 0.969231 +v -0.351223 0.390696 0.976748 +v -0.363193 0.395677 0.969231 +v -0.363193 0.395655 0.976131 +v -0.368152 0.407648 0.969231 +v -0.368152 0.407625 0.974641 +v -0.363193 0.419618 0.969231 +v -0.363193 0.419596 0.973150 +v -0.277128 0.335937 -0.266134 +v -0.277129 0.381259 -0.333962 +v -0.408597 0.188100 -0.807805 +v -0.408597 0.590666 -0.769748 +v -0.344887 0.823475 -0.143261 +v -0.344886 0.823475 0.165252 +v -0.277129 0.397173 -0.413971 +v -0.277129 0.381259 -0.493980 +v -0.408593 0.432986 0.968166 +v -0.408596 0.568670 -0.220098 +v -0.408594 0.541333 0.462026 +v -0.344887 0.823475 -0.351271 +v -0.408593 0.282150 0.995400 +v -0.408596 0.281678 -0.188054 +v -0.446214 0.188100 0.478126 +v -0.446214 0.260083 0.492444 +v -0.446214 0.321107 0.533219 +v -0.446214 0.361882 0.594243 +v -0.446214 0.376200 0.666226 +v -0.446214 0.361882 0.738208 +v -0.446213 0.321107 0.799232 +v -0.446213 0.260083 0.840007 +v -0.446213 0.188100 0.854326 +v -0.446213 0.116117 0.840007 +v -0.446213 0.055093 0.799232 +v -0.446214 0.014318 0.738208 +v -0.446214 0.000000 0.666226 +v -0.446214 0.014318 0.594243 +v -0.446214 0.055093 0.533219 +v -0.446214 0.116117 0.492444 +v -0.446216 0.188100 -0.291706 +v -0.446216 0.116117 -0.587753 +v -0.446216 0.055093 -0.546978 +v -0.446216 0.014318 -0.485954 +v -0.446216 0.000000 -0.413971 +v -0.446216 0.014318 -0.341988 +v -0.446216 0.055093 -0.280964 +v -0.446216 0.116117 -0.240189 +v -0.446216 0.188100 -0.225871 +v -0.446216 0.260083 -0.240189 +v -0.446216 0.321107 -0.280964 +v -0.446216 0.361882 -0.341988 +v -0.446216 0.376200 -0.413971 +v -0.446216 0.361882 -0.485953 +v -0.446216 0.321107 -0.546978 +v -0.446216 0.260083 -0.587752 +v -0.446213 0.188100 0.788491 +v -0.446213 0.234889 0.779184 +v -0.446214 0.274555 0.752680 +v -0.446214 0.301058 0.713014 +v -0.446214 0.310365 0.666226 +v -0.446214 0.301058 0.619437 +v -0.446214 0.274555 0.579771 +v -0.446214 0.234889 0.553267 +v -0.446214 0.188100 0.543961 +v -0.277129 0.335937 -0.561808 +v -0.408594 0.188100 0.421696 +v -0.277129 0.268109 -0.607129 +v -0.408595 0.188100 -0.169441 +v -0.408593 0.188100 0.995400 +v -0.277129 0.188100 -0.623044 +v -0.431827 0.188100 -0.623044 +v -0.408594 0.281678 0.440309 +v -0.408594 0.361009 0.493317 +v -0.408596 0.361009 -0.241062 +v -0.408596 0.188100 -0.658501 +v -0.408596 0.281678 -0.639887 +v -0.408596 0.361009 -0.586880 +v -0.408596 0.414016 -0.507548 +v -0.408596 0.432630 -0.413971 +v -0.408596 0.414016 -0.320393 +v -0.431827 0.268109 -0.607129 +v -0.431827 0.335937 -0.561808 +v -0.431826 0.381259 -0.493980 +v -0.431826 0.397173 -0.413971 +v -0.433049 0.188100 -0.193894 +v -0.433049 0.272320 -0.210646 +v -0.433049 0.343718 -0.258353 +v -0.433049 0.391425 -0.329751 +v -0.433049 0.408177 -0.413971 +v -0.433049 0.391425 -0.498191 +v -0.433049 0.343718 -0.569589 +v -0.433049 0.272320 -0.617295 +v -0.433049 0.188100 -0.634048 +v -0.431826 0.381259 -0.333962 +v -0.431826 0.335937 -0.266134 +v -0.431826 0.268109 -0.220812 +v -0.431826 0.188100 -0.204898 +v -0.295734 0.055093 0.533219 +v -0.446214 0.141311 0.553267 +v -0.446214 0.101646 0.579771 +v -0.446214 0.075142 0.619437 +v -0.446214 0.065835 0.666226 +v -0.446214 0.075142 0.713014 +v -0.446214 0.101646 0.752680 +v -0.446213 0.141311 0.779184 +v -0.277128 0.268109 -0.220813 +v -0.277128 0.188100 -0.204898 +v -0.276926 0.188100 -0.413971 +v -0.431824 0.188100 0.457152 +v -0.277127 0.188100 0.457152 +v -0.277127 0.268109 0.473067 +v -0.277127 0.335937 0.518388 +v -0.277127 0.381259 0.586217 +v -0.277126 0.397173 0.666225 +v -0.277126 0.381259 0.746234 +v -0.277126 0.335937 0.814062 +v -0.408594 0.414016 0.572648 +v -0.408594 0.432630 0.666226 +v -0.408593 0.414016 0.759803 +v -0.408593 0.361009 0.839134 +v -0.408593 0.281678 0.892142 +v -0.408593 0.188100 0.910756 +v -0.431824 0.268109 0.473067 +v -0.431824 0.335937 0.518389 +v -0.431824 0.381259 0.586217 +v -0.431824 0.397173 0.666226 +v -0.433046 0.188100 0.886303 +v -0.433046 0.272320 0.869550 +v -0.433046 0.343718 0.821844 +v -0.433047 0.391425 0.750445 +v -0.433047 0.408177 0.666226 +v -0.433047 0.391425 0.582006 +v -0.433047 0.343718 0.510608 +v -0.433047 0.272320 0.462901 +v -0.433047 0.188100 0.446149 +v -0.431824 0.381259 0.746234 +v -0.431824 0.335937 0.814063 +v -0.431824 0.268109 0.859384 +v -0.431824 0.188100 0.875299 +v -0.277126 0.268109 0.859384 +v -0.277126 0.188100 0.875299 +v -0.276924 0.188100 0.666225 +v -0.295734 0.014318 0.594243 +v -0.295734 0.000000 0.666225 +v -0.295734 0.014318 0.738208 +v -0.295733 0.055093 0.799232 +v -0.295733 0.116117 0.840007 +v -0.295733 0.188100 0.854325 +v -0.295733 0.260083 0.840007 +v -0.295733 0.321107 0.799232 +v -0.295734 0.361882 0.738208 +v -0.295734 0.376200 0.666225 +v -0.295734 0.361882 0.594243 +v -0.295734 0.321107 0.533219 +v -0.295734 0.260083 0.492444 +v -0.295734 0.188100 0.478125 +v -0.295734 0.116117 0.492443 +v -0.295733 0.188100 0.788490 +v -0.295733 0.234889 0.779183 +v -0.295733 0.274555 0.752680 +v -0.295734 0.301058 0.713014 +v -0.295734 0.310365 0.666225 +v -0.295734 0.301058 0.619437 +v -0.295734 0.274555 0.579771 +v -0.295734 0.234889 0.553267 +v -0.295734 0.188100 0.543960 +v -0.446216 0.188100 -0.602071 +v -0.295734 0.141311 0.553267 +v -0.295734 0.101646 0.579771 +v -0.295734 0.075142 0.619437 +v -0.295734 0.065835 0.666225 +v -0.295734 0.075142 0.713014 +v -0.295733 0.101646 0.752680 +v -0.295733 0.141311 0.779183 +v -0.446216 0.234889 -0.301013 +v -0.446216 0.274555 -0.327516 +v -0.446216 0.301058 -0.367182 +v -0.446216 0.310365 -0.413971 +v -0.446216 0.301058 -0.460759 +v -0.446216 0.274555 -0.500425 +v -0.446216 0.234889 -0.526929 +v -0.446216 0.188100 -0.536236 +v -0.295736 0.055093 -0.546978 +v -0.446216 0.141311 -0.526929 +v -0.446216 0.101646 -0.500425 +v -0.446216 0.075142 -0.460760 +v -0.446216 0.065835 -0.413971 +v -0.446216 0.075142 -0.367182 +v -0.446216 0.101646 -0.327516 +v -0.446216 0.141311 -0.301013 +v -0.295736 0.014318 -0.485954 +v -0.295736 0.000000 -0.413971 +v -0.295736 0.014318 -0.341988 +v -0.295736 0.055093 -0.280964 +v -0.295736 0.116117 -0.240189 +v -0.295736 0.188100 -0.225871 +v -0.295736 0.260083 -0.240189 +v -0.295736 0.321107 -0.280964 +v -0.295736 0.361882 -0.341988 +v -0.295736 0.376200 -0.413971 +v -0.295736 0.361882 -0.485954 +v -0.295736 0.321107 -0.546978 +v -0.295736 0.260083 -0.587753 +v -0.295736 0.188100 -0.602071 +v -0.295736 0.116117 -0.587753 +v -0.295736 0.188100 -0.291706 +v -0.295736 0.234889 -0.301013 +v -0.295736 0.274555 -0.327517 +v -0.295736 0.301058 -0.367182 +v -0.295736 0.310365 -0.413971 +v -0.295736 0.301058 -0.460760 +v -0.295736 0.274555 -0.500425 +v -0.295736 0.234889 -0.526929 +v -0.295736 0.188100 -0.536236 +v -0.295736 0.141311 -0.526929 +v -0.295736 0.101646 -0.500425 +v -0.295736 0.075142 -0.460760 +v -0.295736 0.065835 -0.413971 +v -0.295736 0.075142 -0.367182 +v -0.295736 0.101646 -0.327517 +v -0.295736 0.141311 -0.301013 +v -0.436811 0.188100 -0.303932 +v -0.436808 0.188100 0.776264 +v -0.436808 0.230210 0.767888 +v -0.436809 0.265909 0.744035 +v -0.436809 0.289762 0.708335 +v -0.436809 0.298139 0.666226 +v -0.436809 0.289762 0.624116 +v -0.436809 0.265909 0.588417 +v -0.436809 0.230210 0.564563 +v -0.436809 0.188100 0.556187 +v -0.436809 0.188100 0.666226 +v -0.436809 0.145990 0.564563 +v -0.436809 0.110291 0.588417 +v -0.436809 0.086438 0.624116 +v -0.436809 0.078062 0.666226 +v -0.436809 0.086438 0.708335 +v -0.436809 0.110291 0.744035 +v -0.436808 0.145990 0.767888 +v -0.436811 0.230210 -0.312308 +v -0.436811 0.265909 -0.336162 +v -0.436811 0.289762 -0.371861 +v -0.436811 0.298139 -0.413971 +v -0.436811 0.289762 -0.456081 +v -0.436811 0.265909 -0.491780 +v -0.436811 0.230210 -0.515633 +v -0.436811 0.188100 -0.524009 +v -0.436811 0.188100 -0.413971 +v -0.436811 0.145990 -0.515633 +v -0.436811 0.110291 -0.491780 +v -0.436811 0.086438 -0.456081 +v -0.436811 0.078062 -0.413971 +v -0.436811 0.086438 -0.371861 +v -0.436811 0.110291 -0.336162 +v -0.436811 0.145990 -0.312308 +v -0.268459 0.411383 0.969007 +v -0.268459 0.411308 0.983975 +v -0.308361 0.394855 0.969008 +v -0.308361 0.394780 0.985994 +v -0.324889 0.354953 0.969008 +v -0.324889 0.354878 0.990867 +v -0.308361 0.315051 0.969007 +v -0.308361 0.314976 0.995739 +v -0.268459 0.298523 0.969007 +v -0.268459 0.298448 0.997758 +v -0.228557 0.315051 0.969007 +v -0.228557 0.314976 0.995739 +v -0.212029 0.354953 0.969007 +v -0.212029 0.354878 0.990866 +v -0.228557 0.394855 0.969007 +v -0.228557 0.394780 0.985994 +v -0.168766 0.411348 0.971758 +v -0.168766 0.298461 0.985679 +v -0.168766 0.411348 0.983995 +v -0.168766 0.298461 0.997917 +v -0.362512 0.590666 -0.769748 +v -0.362510 0.541333 0.462026 +v -0.352166 0.785855 -0.169237 +v -0.306082 0.785855 -0.418784 +v -0.306080 0.785855 0.204022 +v -0.407744 0.546528 0.414200 +v -0.352165 0.785855 0.155220 +v -0.408596 0.567189 -0.177692 +v -0.352165 0.785855 -0.126805 +v -0.408596 0.578782 -0.429416 +v -0.352166 0.785855 -0.241546 +v -0.380381 0.568670 -0.220098 +v -0.323951 0.785855 -0.169237 +v -0.379529 0.546528 0.414200 +v -0.323950 0.785855 0.155220 +v -0.380381 0.567189 -0.177692 +v -0.323950 0.785855 -0.126805 +v -0.380381 0.578782 -0.429416 +v -0.323951 0.785855 -0.241546 +v -0.361161 0.548934 0.427887 +v -0.308868 0.771454 0.188815 +v -0.362512 0.585756 -0.618209 +v -0.308644 0.772617 -0.405199 +v -0.331383 0.558498 -0.776763 +v -0.331415 0.558498 -0.757993 +v -0.331383 0.485337 -0.784079 +v -0.331415 0.485337 -0.757993 +v -0.348306 0.485337 -0.784079 +v -0.348337 0.485337 -0.757993 +v -0.372675 0.503627 -0.782250 +v -0.372715 0.503627 -0.757993 +v -0.348306 0.558498 -0.776763 +v -0.372675 0.540208 -0.778592 +v 0.361163 0.548934 0.427886 +v 0.308868 0.771454 0.188814 +v 0.308642 0.772617 -0.405200 +v -0.348337 0.558498 -0.757993 +v -0.372715 0.540208 -0.757993 +v 0.245550 0.540208 -0.778593 +v 0.245550 0.540208 -0.778593 +v 0.269927 0.558498 -0.776764 +v 0.269927 0.558498 -0.776764 +v 0.339256 0.419596 0.973149 +v 0.339256 0.419596 0.973149 +v 0.351226 0.424554 0.972532 +v 0.351226 0.424554 0.972532 +v 0.316182 0.485337 -0.757995 +v 0.316150 0.485337 -0.784080 +v 0.316150 0.485337 -0.784080 +v 0.269927 0.485337 -0.784080 +v 0.269927 0.485337 -0.784080 +v 0.269959 0.485337 -0.757995 +v 0.245550 0.503627 -0.782251 +v 0.245550 0.503627 -0.782251 +v 0.245590 0.503627 -0.757994 +v 0.351226 0.424577 0.969229 +v 0.339256 0.419618 0.969229 +v 0.316150 0.558498 -0.776764 +v 0.316150 0.558498 -0.776764 +v 0.334297 0.407625 0.974639 +v 0.334297 0.407625 0.974639 +v 0.334297 0.407648 0.969229 +v 0.339256 0.395655 0.976130 +v 0.339256 0.395655 0.976130 +v 0.339256 0.395677 0.969229 +v 0.351226 0.390696 0.976747 +v 0.351226 0.390696 0.976747 +v 0.351226 0.390719 0.969229 +v 0.363197 0.395655 0.976129 +v 0.363197 0.395655 0.976129 +v 0.363197 0.395677 0.969229 +v 0.368155 0.407625 0.974639 +v 0.368155 0.407625 0.974639 +v 0.368155 0.407648 0.969229 +v 0.277126 0.397173 -0.413972 +v 0.277126 0.381259 -0.493981 +v 0.408594 0.578782 -0.429418 +v 0.408594 0.578782 -0.429418 +v 0.408594 0.568670 -0.220099 +v 0.408594 0.568670 -0.220099 +v 0.408594 0.414016 -0.320395 +v 0.408594 0.361009 -0.241064 +v 0.446216 0.188100 0.543959 +v 0.446216 0.141311 0.553266 +v 0.446216 0.101646 0.579769 +v 0.446216 0.075142 0.619435 +v 0.446216 0.065835 0.666224 +v 0.446216 0.260083 0.492442 +v 0.446216 0.188100 0.478124 +v 0.446216 0.321107 0.533217 +v 0.446216 0.361882 0.594241 +v 0.446216 0.376200 0.666224 +v 0.446216 0.361882 0.738207 +v 0.446216 0.321107 0.799231 +v 0.446216 0.260083 0.840006 +v 0.446216 0.188100 0.854324 +v 0.446216 0.116117 0.840006 +v 0.446216 0.055093 0.799231 +v 0.446216 0.014318 0.738207 +v 0.446216 0.000000 0.666224 +v 0.446216 0.014318 0.594241 +v 0.446216 0.055093 0.533217 +v 0.446216 0.116117 0.492442 +v 0.446214 0.274555 -0.500427 +v 0.446213 0.234889 -0.526931 +v 0.446214 0.310365 -0.413972 +v 0.446214 0.301058 -0.460761 +v 0.446213 0.188100 -0.602072 +v 0.446213 0.116117 -0.587754 +v 0.446213 0.055093 -0.546979 +v 0.446214 0.014318 -0.485955 +v 0.446214 0.000000 -0.413972 +v 0.446214 0.014318 -0.341990 +v 0.446216 0.075142 0.713013 +v 0.446216 0.101646 0.752678 +v 0.446216 0.141311 0.779182 +v 0.446216 0.188100 0.788489 +v 0.446216 0.234889 0.779182 +v 0.446216 0.274555 0.752678 +v 0.446216 0.301058 0.713013 +v 0.446216 0.310365 0.666224 +v 0.277126 0.268109 -0.607130 +v 0.277126 0.188100 -0.623045 +v 0.408594 0.188100 -0.169442 +v 0.408594 0.281678 -0.188056 +v 0.277127 0.188100 -0.204899 +v 0.277127 0.268109 -0.220814 +v 0.446216 0.301058 0.619435 +v 0.408597 0.188100 0.995399 +v 0.408597 0.282150 0.995399 +v 0.446216 0.274555 0.579769 +v 0.277126 0.335937 -0.561809 +v 0.446216 0.234889 0.553266 +v 0.408596 0.188100 0.421694 +v 0.408596 0.281678 0.440308 +v 0.408596 0.361009 0.493315 +v 0.408596 0.414016 0.572646 +v 0.408594 0.414016 -0.507550 +v 0.408593 0.361009 -0.586881 +v 0.408594 0.432630 -0.413972 +v 0.431824 0.268109 -0.607131 +v 0.431824 0.188100 -0.623046 +v 0.277127 0.335937 -0.266135 +v 0.277127 0.381259 -0.333963 +v 0.431824 0.335937 -0.561809 +v 0.431824 0.381259 -0.493981 +v 0.433047 0.188100 -0.193895 +v 0.433047 0.272320 -0.210648 +v 0.433047 0.343718 -0.258354 +v 0.433047 0.391425 -0.329753 +v 0.431825 0.268109 -0.220814 +v 0.431824 0.335937 -0.266135 +v 0.277128 0.335937 0.518387 +v 0.277128 0.268109 0.473066 +v 0.277129 0.381259 0.586215 +v 0.277129 0.188100 0.875297 +v 0.277129 0.268109 0.859383 +v 0.277128 0.188100 0.457151 +v 0.277129 0.397173 0.666224 +v 0.277129 0.381259 0.746233 +v 0.408596 0.188100 0.910754 +v 0.408596 0.281678 0.892140 +v 0.431826 0.268109 0.473065 +v 0.431826 0.188100 0.457151 +v 0.277129 0.335937 0.814061 +v 0.431826 0.335937 0.518387 +v 0.431826 0.381259 0.586215 +v 0.433049 0.391425 0.750444 +v 0.433049 0.408177 0.666224 +v 0.433049 0.343718 0.510606 +v 0.433049 0.272320 0.462899 +v 0.433049 0.188100 0.446147 +v 0.431827 0.381259 0.746233 +v 0.431826 0.397173 0.666224 +v 0.295736 0.188100 0.478124 +v 0.295736 0.116117 0.492442 +v 0.295736 0.055093 0.533217 +v 0.295736 0.014318 0.594241 +v 0.295736 0.000000 0.666224 +v 0.295736 0.014318 0.738207 +v 0.295736 0.055093 0.799231 +v 0.295736 0.116117 0.840006 +v 0.295736 0.188100 0.854324 +v 0.295736 0.260083 0.840006 +v 0.295736 0.321107 0.799231 +v 0.295736 0.361882 0.738207 +v 0.295736 0.376200 0.666224 +v 0.295736 0.361882 0.594241 +v 0.295736 0.321107 0.533217 +v 0.295736 0.260083 0.492442 +v 0.446214 0.055093 -0.280966 +v 0.446214 0.116117 -0.240191 +v 0.446214 0.188100 -0.225873 +v 0.446214 0.260083 -0.240191 +v 0.446214 0.321107 -0.280966 +v 0.446214 0.361882 -0.341990 +v 0.446214 0.376200 -0.413972 +v 0.446214 0.361882 -0.485955 +v 0.446213 0.321107 -0.546979 +v 0.446213 0.260083 -0.587754 +v 0.446213 0.141311 -0.526931 +v 0.446214 0.101646 -0.500427 +v 0.295733 0.188100 -0.602072 +v 0.295733 0.116117 -0.587754 +v 0.295733 0.055093 -0.546979 +v 0.295734 0.014318 -0.485955 +v 0.295734 0.000000 -0.413972 +v 0.295734 0.014318 -0.341989 +v 0.295734 0.055093 -0.280965 +v 0.295734 0.116117 -0.240190 +v 0.295734 0.188100 -0.225872 +v 0.295734 0.260083 -0.240191 +v 0.295734 0.321107 -0.280965 +v 0.295734 0.361882 -0.341989 +v 0.295734 0.376200 -0.413972 +v 0.295734 0.361882 -0.485955 +v 0.295733 0.321107 -0.546979 +v 0.295733 0.260083 -0.587754 +v 0.436811 0.188100 0.556185 +v 0.436811 0.145990 0.564562 +v 0.436811 0.110291 0.588415 +v 0.436811 0.086438 0.624114 +v 0.436811 0.078062 0.666224 +v 0.436809 0.265909 -0.491781 +v 0.436809 0.230210 -0.515635 +v 0.436809 0.298139 -0.413972 +v 0.436809 0.289762 -0.456082 +v 0.436809 0.289762 -0.371863 +v 0.436809 0.265909 -0.336163 +v 0.436809 0.230210 -0.312310 +v 0.436809 0.188100 -0.303934 +v 0.436809 0.145990 -0.312310 +v 0.436809 0.110291 -0.336163 +v 0.436809 0.086438 -0.371863 +v 0.436809 0.078062 -0.413972 +v 0.436811 0.086438 0.708334 +v 0.436811 0.110291 0.744033 +v 0.436811 0.145990 0.767886 +v 0.436811 0.188100 0.776262 +v 0.436811 0.230210 0.767886 +v 0.436811 0.265909 0.744033 +v 0.436811 0.289762 0.708334 +v 0.436811 0.298139 0.666224 +v 0.436811 0.289762 0.624114 +v 0.436811 0.265909 0.588415 +v 0.436811 0.230210 0.564562 +v 0.436809 0.086438 -0.456082 +v 0.436809 0.110291 -0.491781 +v 0.436809 0.145990 -0.515635 +v 0.436809 0.188100 -0.524011 +v 0.408597 0.432986 0.968164 +v 0.408597 0.432986 0.968164 +v 0.408596 0.541334 0.462025 +v 0.408596 0.541334 0.462025 +v 0.308364 0.394780 0.985993 +v 0.308364 0.394780 0.985993 +v 0.268462 0.411308 0.983974 +v 0.268462 0.411308 0.983974 +v 0.268462 0.411383 0.969006 +v 0.308364 0.394855 0.969006 +v 0.324892 0.354878 0.990865 +v 0.324892 0.354878 0.990865 +v 0.324892 0.354953 0.969006 +v 0.308364 0.314976 0.995738 +v 0.308364 0.314976 0.995738 +v 0.308364 0.315051 0.969006 +v 0.268462 0.298448 0.997757 +v 0.268462 0.298448 0.997757 +v 0.268462 0.298523 0.969006 +v 0.228560 0.314976 0.995738 +v 0.228560 0.314976 0.995738 +v 0.228560 0.315051 0.969006 +v 0.212032 0.354878 0.990866 +v 0.212032 0.354878 0.990866 +v 0.212032 0.354953 0.969007 +v 0.168769 0.298461 0.997916 +v 0.168769 0.298461 0.997916 +v 0.168769 0.411348 0.983994 +v 0.168769 0.411348 0.983994 +v 0.168769 0.298461 0.985679 +v 0.168769 0.411348 0.971757 +v 0.306079 0.785855 -0.418785 +v 0.306079 0.785855 -0.418785 +v 0.362509 0.590666 -0.769750 +v 0.362509 0.590666 -0.769750 +v 0.362509 0.590666 -0.769750 +v 0.380379 0.578782 -0.429418 +v 0.380379 0.578782 -0.429418 +v 0.380379 0.568670 -0.220099 +v 0.380379 0.568670 -0.220099 +v 0.323949 0.785855 -0.169238 +v 0.323949 0.785855 -0.169238 +v 0.323949 0.785855 -0.241547 +v 0.323949 0.785855 -0.241547 +v 0.323950 0.785855 0.155219 +v 0.323950 0.785855 0.155219 +v 0.379530 0.546528 0.414199 +v 0.379530 0.546528 0.414199 +v 0.380379 0.567189 -0.177693 +v 0.380379 0.567189 -0.177693 +v 0.323950 0.785855 -0.126806 +v 0.323950 0.785855 -0.126806 +v 0.362509 0.585756 -0.618210 +v 0.362509 0.585756 -0.618210 +v 0.348302 0.485337 -0.784080 +v 0.348302 0.485337 -0.784080 +v 0.372671 0.503627 -0.782251 +v 0.372671 0.503627 -0.782251 +v 0.331380 0.485337 -0.784080 +v 0.331380 0.485337 -0.784080 +v 0.331411 0.485337 -0.757995 +v 0.344885 0.823475 -0.351272 +v 0.344886 0.823475 -0.143262 +v 0.344887 0.823475 0.165251 +v 0.344887 0.823475 0.165251 +v 0.433047 0.408177 -0.413972 +v 0.408593 0.590666 -0.769750 +v 0.408593 0.590666 -0.769750 +v 0.408593 0.188100 -0.807807 +v 0.372671 0.540208 -0.778593 +v 0.372671 0.540208 -0.778593 +v 0.348302 0.558498 -0.776764 +v 0.348302 0.558498 -0.776764 +v 0.363197 0.419596 0.973149 +v 0.363197 0.419596 0.973149 +v 0.363197 0.419618 0.969229 +v 0.433046 0.343718 -0.569590 +v 0.433046 0.272320 -0.617297 +v 0.433046 0.188100 -0.634049 +v 0.431825 0.188100 -0.204899 +v 0.446213 0.188100 -0.536238 +v 0.245590 0.540208 -0.757994 +v 0.446214 0.301058 -0.367184 +v 0.446214 0.274555 -0.327518 +v 0.446214 0.234889 -0.301014 +v 0.446214 0.188100 -0.291707 +v 0.446214 0.141311 -0.301014 +v 0.446214 0.101646 -0.327518 +v 0.446214 0.075142 -0.367184 +v 0.446214 0.065835 -0.413972 +v 0.408593 0.281678 -0.639889 +v 0.408593 0.188100 -0.658502 +v 0.431824 0.397173 -0.413972 +v 0.433047 0.391425 -0.498192 +v 0.431824 0.381259 -0.333964 +v 0.408596 0.432630 0.666224 +v 0.408596 0.414016 0.759802 +v 0.408596 0.361009 0.839133 +v 0.433049 0.188100 0.886301 +v 0.433049 0.272320 0.869549 +v 0.433049 0.343718 0.821842 +v 0.433049 0.391425 0.582004 +v 0.431827 0.335937 0.814061 +v 0.431827 0.268109 0.859382 +v 0.431827 0.188100 0.875297 +v 0.446214 0.075142 -0.460761 +v 0.228560 0.394780 0.985993 +v 0.228560 0.394780 0.985993 +v 0.228560 0.394855 0.969007 +v 0.362511 0.541334 0.462025 +v 0.362511 0.541334 0.462025 +v 0.306081 0.785855 0.204021 +v 0.306081 0.785855 0.204021 +v 0.352164 0.785855 -0.241547 +v 0.352164 0.785855 -0.241547 +v 0.352164 0.785855 -0.169238 +v 0.352164 0.785855 -0.169238 +v 0.352165 0.785855 0.155219 +v 0.352165 0.785855 0.155219 +v 0.407745 0.546528 0.414199 +v 0.407745 0.546528 0.414199 +v 0.408594 0.567189 -0.177693 +v 0.408594 0.567189 -0.177693 +v 0.352165 0.785855 -0.126806 +v 0.352165 0.785855 -0.126806 +v 0.331380 0.558498 -0.776764 +v 0.331380 0.558498 -0.776764 +v 0.331411 0.558498 -0.757995 +v 0.348334 0.485337 -0.757995 +v 0.372711 0.503627 -0.757995 +v 0.361163 0.548934 0.427886 +v 0.361163 0.548934 0.427886 +v 0.308868 0.771454 0.188814 +v 0.308868 0.771454 0.188814 +v 0.316182 0.558498 -0.757995 +v 0.269959 0.558498 -0.757995 +v 0.372711 0.540208 -0.757995 +v 0.348334 0.558498 -0.757995 +v -0.245553 0.540208 -0.778592 +v -0.245553 0.540208 -0.778592 +v -0.269931 0.558498 -0.776763 +v -0.269931 0.558498 -0.776763 +v -0.339252 0.419596 0.973150 +v -0.339252 0.419596 0.973150 +v -0.351223 0.424554 0.972533 +v -0.351223 0.424554 0.972533 +v -0.316186 0.485337 -0.757993 +v -0.316154 0.485337 -0.784079 +v -0.316154 0.485337 -0.784079 +v -0.269931 0.485337 -0.784079 +v -0.269931 0.485337 -0.784079 +v -0.269962 0.485337 -0.757994 +v -0.245553 0.503627 -0.782250 +v -0.245553 0.503627 -0.782250 +v -0.245594 0.503627 -0.757993 +v -0.351223 0.424577 0.969231 +v -0.339252 0.419618 0.969230 +v -0.316154 0.558498 -0.776763 +v -0.316154 0.558498 -0.776763 +v -0.334294 0.407625 0.974641 +v -0.334294 0.407625 0.974641 +v -0.334294 0.407648 0.969230 +v -0.339252 0.395655 0.976131 +v -0.339252 0.395655 0.976131 +v -0.339252 0.395677 0.969230 +v -0.351223 0.390696 0.976748 +v -0.351223 0.390696 0.976748 +v -0.351223 0.390719 0.969231 +v -0.363193 0.395655 0.976131 +v -0.363193 0.395655 0.976131 +v -0.363193 0.395677 0.969231 +v -0.368152 0.407625 0.974641 +v -0.368152 0.407625 0.974641 +v -0.368152 0.407648 0.969231 +v -0.277129 0.397173 -0.413971 +v -0.277129 0.381259 -0.493980 +v -0.408596 0.578782 -0.429416 +v -0.408596 0.578782 -0.429416 +v -0.408596 0.568670 -0.220098 +v -0.408596 0.568670 -0.220098 +v -0.408596 0.414016 -0.320393 +v -0.408596 0.361009 -0.241062 +v -0.446214 0.188100 0.543961 +v -0.446214 0.141311 0.553267 +v -0.446214 0.101646 0.579771 +v -0.446214 0.075142 0.619437 +v -0.446214 0.065835 0.666226 +v -0.446214 0.260083 0.492444 +v -0.446214 0.188100 0.478126 +v -0.446214 0.321107 0.533219 +v -0.446214 0.361882 0.594243 +v -0.446214 0.376200 0.666226 +v -0.446214 0.361882 0.738208 +v -0.446213 0.321107 0.799232 +v -0.446213 0.260083 0.840007 +v -0.446213 0.188100 0.854326 +v -0.446213 0.116117 0.840007 +v -0.446213 0.055093 0.799232 +v -0.446214 0.014318 0.738208 +v -0.446214 0.000000 0.666226 +v -0.446214 0.014318 0.594243 +v -0.446214 0.055093 0.533219 +v -0.446214 0.116117 0.492444 +v -0.446216 0.274555 -0.500425 +v -0.446216 0.234889 -0.526929 +v -0.446216 0.310365 -0.413971 +v -0.446216 0.301058 -0.460759 +v -0.446216 0.188100 -0.602071 +v -0.446216 0.116117 -0.587753 +v -0.446216 0.055093 -0.546978 +v -0.446216 0.014318 -0.485954 +v -0.446216 0.000000 -0.413971 +v -0.446216 0.014318 -0.341988 +v -0.446214 0.075142 0.713014 +v -0.446214 0.101646 0.752680 +v -0.446213 0.141311 0.779184 +v -0.446213 0.188100 0.788491 +v -0.446213 0.234889 0.779184 +v -0.446214 0.274555 0.752680 +v -0.446214 0.301058 0.713014 +v -0.446214 0.310365 0.666226 +v -0.277129 0.268109 -0.607129 +v -0.277129 0.188100 -0.623044 +v -0.408595 0.188100 -0.169441 +v -0.408596 0.281678 -0.188054 +v -0.277128 0.188100 -0.204898 +v -0.277128 0.268109 -0.220813 +v -0.446214 0.301058 0.619437 +v -0.408593 0.188100 0.995400 +v -0.408593 0.282150 0.995400 +v -0.446214 0.274555 0.579771 +v -0.277129 0.335937 -0.561808 +v -0.446214 0.234889 0.553267 +v -0.408594 0.188100 0.421696 +v -0.408594 0.281678 0.440309 +v -0.408594 0.361009 0.493317 +v -0.408594 0.414016 0.572648 +v -0.408596 0.414016 -0.507548 +v -0.408596 0.361009 -0.586880 +v -0.408596 0.432630 -0.413971 +v -0.431827 0.268109 -0.607129 +v -0.431827 0.188100 -0.623044 +v -0.277128 0.335937 -0.266134 +v -0.277129 0.381259 -0.333962 +v -0.431827 0.335937 -0.561808 +v -0.431826 0.381259 -0.493980 +v -0.433049 0.188100 -0.193894 +v -0.433049 0.272320 -0.210646 +v -0.433049 0.343718 -0.258353 +v -0.433049 0.391425 -0.329751 +v -0.431826 0.268109 -0.220812 +v -0.431826 0.335937 -0.266134 +v -0.277127 0.335937 0.518388 +v -0.277127 0.268109 0.473067 +v -0.277127 0.381259 0.586217 +v -0.277126 0.188100 0.875299 +v -0.277126 0.268109 0.859384 +v -0.277127 0.188100 0.457152 +v -0.277126 0.397173 0.666225 +v -0.277126 0.381259 0.746234 +v -0.408593 0.188100 0.910756 +v -0.408593 0.281678 0.892142 +v -0.431824 0.268109 0.473067 +v -0.431824 0.188100 0.457152 +v -0.277126 0.335937 0.814062 +v -0.431824 0.335937 0.518389 +v -0.431824 0.381259 0.586217 +v -0.433047 0.391425 0.750445 +v -0.433047 0.408177 0.666226 +v -0.433047 0.343718 0.510608 +v -0.433047 0.272320 0.462901 +v -0.433047 0.188100 0.446149 +v -0.431824 0.381259 0.746234 +v -0.431824 0.397173 0.666226 +v -0.295734 0.188100 0.478125 +v -0.295734 0.116117 0.492443 +v -0.295734 0.055093 0.533219 +v -0.295734 0.014318 0.594243 +v -0.295734 0.000000 0.666225 +v -0.295734 0.014318 0.738208 +v -0.295733 0.055093 0.799232 +v -0.295733 0.116117 0.840007 +v -0.295733 0.188100 0.854325 +v -0.295733 0.260083 0.840007 +v -0.295733 0.321107 0.799232 +v -0.295734 0.361882 0.738208 +v -0.295734 0.376200 0.666225 +v -0.295734 0.361882 0.594243 +v -0.295734 0.321107 0.533219 +v -0.295734 0.260083 0.492444 +v -0.446216 0.055093 -0.280964 +v -0.446216 0.116117 -0.240189 +v -0.446216 0.188100 -0.225871 +v -0.446216 0.260083 -0.240189 +v -0.446216 0.321107 -0.280964 +v -0.446216 0.361882 -0.341988 +v -0.446216 0.376200 -0.413971 +v -0.446216 0.361882 -0.485953 +v -0.446216 0.321107 -0.546978 +v -0.446216 0.260083 -0.587752 +v -0.446216 0.141311 -0.526929 +v -0.446216 0.101646 -0.500425 +v -0.295736 0.188100 -0.602071 +v -0.295736 0.116117 -0.587753 +v -0.295736 0.055093 -0.546978 +v -0.295736 0.014318 -0.485954 +v -0.295736 0.000000 -0.413971 +v -0.295736 0.014318 -0.341988 +v -0.295736 0.055093 -0.280964 +v -0.295736 0.116117 -0.240189 +v -0.295736 0.188100 -0.225871 +v -0.295736 0.260083 -0.240189 +v -0.295736 0.321107 -0.280964 +v -0.295736 0.361882 -0.341988 +v -0.295736 0.376200 -0.413971 +v -0.295736 0.361882 -0.485954 +v -0.295736 0.321107 -0.546978 +v -0.295736 0.260083 -0.587753 +v -0.436809 0.188100 0.556187 +v -0.436809 0.145990 0.564563 +v -0.436809 0.110291 0.588417 +v -0.436809 0.086438 0.624116 +v -0.436809 0.078062 0.666226 +v -0.436811 0.265909 -0.491780 +v -0.436811 0.230210 -0.515633 +v -0.436811 0.298139 -0.413971 +v -0.436811 0.289762 -0.456081 +v -0.436811 0.289762 -0.371861 +v -0.436811 0.265909 -0.336162 +v -0.436811 0.230210 -0.312308 +v -0.436811 0.188100 -0.303932 +v -0.436811 0.145990 -0.312308 +v -0.436811 0.110291 -0.336162 +v -0.436811 0.086438 -0.371861 +v -0.436811 0.078062 -0.413971 +v -0.436809 0.086438 0.708335 +v -0.436809 0.110291 0.744035 +v -0.436808 0.145990 0.767888 +v -0.436808 0.188100 0.776264 +v -0.436808 0.230210 0.767888 +v -0.436809 0.265909 0.744035 +v -0.436809 0.289762 0.708335 +v -0.436809 0.298139 0.666226 +v -0.436809 0.289762 0.624116 +v -0.436809 0.265909 0.588417 +v -0.436809 0.230210 0.564563 +v -0.436811 0.086438 -0.456081 +v -0.436811 0.110291 -0.491780 +v -0.436811 0.145990 -0.515633 +v -0.436811 0.188100 -0.524009 +v -0.408593 0.432986 0.968166 +v -0.408593 0.432986 0.968166 +v -0.408594 0.541333 0.462026 +v -0.408594 0.541333 0.462026 +v -0.308361 0.394780 0.985994 +v -0.308361 0.394780 0.985994 +v -0.268459 0.411308 0.983975 +v -0.268459 0.411308 0.983975 +v -0.268459 0.411383 0.969007 +v -0.308361 0.394855 0.969008 +v -0.324889 0.354878 0.990867 +v -0.324889 0.354878 0.990867 +v -0.324889 0.354953 0.969008 +v -0.308361 0.314976 0.995739 +v -0.308361 0.314976 0.995739 +v -0.308361 0.315051 0.969007 +v -0.268459 0.298448 0.997758 +v -0.268459 0.298448 0.997758 +v -0.268459 0.298523 0.969007 +v -0.228557 0.314976 0.995739 +v -0.228557 0.314976 0.995739 +v -0.228557 0.315051 0.969007 +v -0.212029 0.354878 0.990866 +v -0.212029 0.354878 0.990866 +v -0.212029 0.354953 0.969007 +v -0.168766 0.298461 0.997917 +v -0.168766 0.298461 0.997917 +v -0.168766 0.411348 0.983995 +v -0.168766 0.411348 0.983995 +v -0.168766 0.298461 0.985679 +v -0.168766 0.411348 0.971758 +v -0.306082 0.785855 -0.418784 +v -0.306082 0.785855 -0.418784 +v -0.362512 0.590666 -0.769748 +v -0.362512 0.590666 -0.769748 +v -0.362512 0.590666 -0.769748 +v -0.380381 0.578782 -0.429416 +v -0.380381 0.578782 -0.429416 +v -0.380381 0.568670 -0.220098 +v -0.380381 0.568670 -0.220098 +v -0.323951 0.785855 -0.169237 +v -0.323951 0.785855 -0.169237 +v -0.323951 0.785855 -0.241546 +v -0.323951 0.785855 -0.241546 +v -0.323950 0.785855 0.155220 +v -0.323950 0.785855 0.155220 +v -0.379529 0.546528 0.414200 +v -0.379529 0.546528 0.414200 +v -0.380381 0.567189 -0.177692 +v -0.380381 0.567189 -0.177692 +v -0.323950 0.785855 -0.126805 +v -0.323950 0.785855 -0.126805 +v -0.308868 0.771454 0.188815 +v -0.308868 0.771454 0.188815 +v -0.361161 0.548934 0.427887 +v -0.361161 0.548934 0.427887 +v -0.306080 0.785855 0.204022 +v -0.306080 0.785855 0.204022 +v -0.308644 0.772617 -0.405199 +v -0.308644 0.772617 -0.405199 +v -0.362512 0.585756 -0.618209 +v -0.362512 0.585756 -0.618209 +v -0.348306 0.485337 -0.784079 +v -0.348306 0.485337 -0.784079 +v -0.372675 0.503627 -0.782250 +v -0.372675 0.503627 -0.782250 +v -0.331383 0.485337 -0.784079 +v -0.331383 0.485337 -0.784079 +v -0.331415 0.485337 -0.757993 +v -0.344887 0.823475 -0.351271 +v -0.344887 0.823475 -0.143261 +v -0.344886 0.823475 0.165252 +v -0.344886 0.823475 0.165252 +v -0.433049 0.408177 -0.413971 +v -0.408597 0.590666 -0.769748 +v -0.408597 0.590666 -0.769748 +v -0.408597 0.188100 -0.807805 +v -0.372675 0.540208 -0.778592 +v -0.372675 0.540208 -0.778592 +v -0.348306 0.558498 -0.776763 +v -0.348306 0.558498 -0.776763 +v -0.363193 0.419596 0.973150 +v -0.363193 0.419596 0.973150 +v -0.363193 0.419618 0.969231 +v -0.433049 0.343718 -0.569589 +v -0.433049 0.272320 -0.617295 +v -0.433049 0.188100 -0.634048 +v -0.431826 0.188100 -0.204898 +v -0.446216 0.188100 -0.536236 +v -0.245594 0.540208 -0.757993 +v -0.446216 0.301058 -0.367182 +v -0.446216 0.274555 -0.327516 +v -0.446216 0.234889 -0.301013 +v -0.446216 0.188100 -0.291706 +v -0.446216 0.141311 -0.301013 +v -0.446216 0.101646 -0.327516 +v -0.446216 0.075142 -0.367182 +v -0.446216 0.065835 -0.413971 +v -0.408596 0.281678 -0.639887 +v -0.408596 0.188100 -0.658501 +v -0.431826 0.397173 -0.413971 +v -0.433049 0.391425 -0.498191 +v -0.431826 0.381259 -0.333962 +v -0.408594 0.432630 0.666226 +v -0.408593 0.414016 0.759803 +v -0.408593 0.361009 0.839134 +v -0.433046 0.188100 0.886303 +v -0.433046 0.272320 0.869550 +v -0.433046 0.343718 0.821844 +v -0.433047 0.391425 0.582006 +v -0.431824 0.335937 0.814063 +v -0.431824 0.268109 0.859384 +v -0.431824 0.188100 0.875299 +v -0.446216 0.075142 -0.460760 +v -0.228557 0.394780 0.985994 +v -0.228557 0.394780 0.985994 +v -0.228557 0.394855 0.969007 +v -0.362510 0.541333 0.462026 +v -0.362510 0.541333 0.462026 +v -0.352166 0.785855 -0.241546 +v -0.352166 0.785855 -0.241546 +v -0.352166 0.785855 -0.169237 +v -0.352166 0.785855 -0.169237 +v -0.352165 0.785855 0.155220 +v -0.352165 0.785855 0.155220 +v -0.407744 0.546528 0.414200 +v -0.407744 0.546528 0.414200 +v -0.408596 0.567189 -0.177692 +v -0.408596 0.567189 -0.177692 +v -0.352165 0.785855 -0.126805 +v -0.352165 0.785855 -0.126805 +v -0.331383 0.558498 -0.776763 +v -0.331383 0.558498 -0.776763 +v -0.331415 0.558498 -0.757993 +v -0.348337 0.485337 -0.757993 +v -0.372715 0.503627 -0.757993 +v 0.308642 0.772617 -0.405200 +v 0.308642 0.772617 -0.405200 +v -0.316186 0.558498 -0.757993 +v -0.269962 0.558498 -0.757994 +v -0.372715 0.540208 -0.757993 +v -0.348337 0.558498 -0.757993 +g car-hatchback-blue_Cube.031_carmaterial_blue +usemtl carmaterial_blue +s 1 +f 114 115 125 +f 116 126 125 +f 110 101 100 +f 111 100 90 +f 88 141 140 +f 149 155 145 +f 152 143 142 +f 153 142 128 +f 136 92 300 +f 92 91 42 +f 85 87 42 +f 110 964 102 +f 106 105 771 +f 105 104 950 +f 114 29 30 +f 961 948 949 +f 96 947 948 +f 97 109 947 +f 98 936 109 +f 103 35 36 +f 99 699 993 +f 107 106 772 +f 964 108 963 +f 766 36 84 +f 765 84 86 +f 761 86 89 +f 158 160 159 +f 793 156 135 +f 147 969 975 +f 971 147 157 +f 152 972 144 +f 974 159 135 +f 763 747 127 +f 764 763 127 +f 742 752 127 +f 752 696 127 +f 747 746 127 +f 743 742 127 +f 696 695 127 +f 137 789 151 +f 967 788 789 +f 967 139 148 +f 794 133 132 +f 782 970 148 +f 782 781 146 +f 793 134 133 +f 149 971 973 +f 972 150 145 +f 786 787 132 +f 786 131 130 +f 783 130 129 +f 785 777 161 +f 780 785 161 +f 774 773 161 +f 773 775 161 +f 777 776 161 +f 778 774 161 +f 775 779 161 +f 779 780 161 +f 37 138 966 +f 37 966 136 +f 992 994 921 +f 697 984 913 +f 988 915 922 +f 302 920 917 +f 990 918 916 +f 986 911 914 +f 304 908 909 +f 700 910 912 +f 995 993 699 +f 699 99 760 +f 107 113 963 +f 701 770 936 +f 103 965 30 +f 37 968 138 +f 41 140 968 +f 695 764 127 +f 993 300 92 +f 33 34 989 +f 32 698 760 +f 297 985 40 +f 33 995 297 +f 31 32 759 +f 95 962 31 +f 32 758 759 +f 905 938 939 +f 871 873 296 +f 701 702 769 +f 702 745 768 +f 745 744 767 +f 755 791 792 +f 755 756 790 +f 756 757 151 +f 989 34 39 +f 934 299 980 +f 932 937 295 +f 32 40 985 +f 355 451 441 +f 451 452 442 +f 426 427 436 +f 416 426 437 +f 466 467 414 +f 471 481 475 +f 468 469 478 +f 454 468 479 +f 643 642 621 +f 462 365 626 +f 419 368 417 +f 368 413 411 +f 436 427 428 +f 432 1122 1121 +f 431 1121 1308 +f 356 355 440 +f 420 1307 1306 +f 1306 1305 422 +f 1305 435 423 +f 435 1294 424 +f 362 361 429 +f 1349 1049 425 +f 433 439 1122 +f 1322 428 1321 +f 410 362 1116 +f 412 410 1115 +f 1112 415 412 +f 485 486 484 +f 460 461 482 +f 473 483 1333 +f 1329 1331 483 +f 469 470 1330 +f 461 485 1332 +f 1113 453 1097 +f 1114 453 1113 +f 1092 453 1102 +f 1102 453 1046 +f 1097 453 1096 +f 1093 453 1092 +f 1046 453 1045 +f 477 1139 463 +f 1139 1138 1325 +f 1325 1138 474 +f 1137 458 459 +f 465 474 1328 +f 1132 1328 472 +f 459 460 1143 +f 481 1331 1329 +f 1330 470 471 +f 457 458 1137 +f 456 457 1136 +f 1134 455 456 +f 1135 487 1127 +f 1130 487 1135 +f 1124 487 1123 +f 1123 487 1125 +f 1127 487 1126 +f 1128 487 1124 +f 1125 487 1129 +f 1129 487 1130 +f 363 1324 464 +f 462 1324 363 +f 1348 1269 1271 +f 1047 1257 1263 +f 1272 1265 1344 +f 1267 1270 628 +f 1266 1268 1346 +f 631 1264 1261 +f 1259 1258 630 +f 1338 640 641 +f 1262 1260 1050 +f 1049 1349 1351 +f 1110 425 1049 +f 1321 439 433 +f 1294 1120 1051 +f 361 356 1323 +f 363 464 1326 +f 1326 466 367 +f 1045 453 1114 +f 1349 419 418 +f 359 1351 1345 +f 1110 1048 358 +f 623 359 366 +f 359 623 1351 +f 1109 358 357 +f 421 357 1320 +f 358 1109 1108 +f 1254 1297 1296 +f 1221 622 1223 +f 1051 1120 1119 +f 1052 1119 1118 +f 1095 1118 1117 +f 1142 1141 1105 +f 1141 1140 1106 +f 1140 477 1107 +f 1345 626 365 +f 1339 1277 1292 +f 1290 1252 1256 +f 1341 366 358 +f 904 314 1357 +f 1278 1274 1003 +f 906 1255 1282 +f 903 1358 1280 +f 296 1001 1276 +f 905 939 1297 +f 749 750 1100 +f 871 296 622 +f 750 872 1222 +f 934 1292 1277 +f 298 1252 1290 +f 935 933 1291 +f 933 932 1290 +f 981 983 1004 +f 29 114 125 +f 115 116 125 +f 111 110 100 +f 112 111 90 +f 41 88 140 +f 150 149 145 +f 153 152 142 +f 154 153 128 +f 39 136 300 +f 93 92 42 +f 91 85 42 +f 101 110 102 +f 772 106 771 +f 771 105 950 +f 965 114 30 +f 94 961 949 +f 961 96 948 +f 96 97 947 +f 97 98 109 +f 766 103 36 +f 93 99 993 +f 113 107 772 +f 102 964 963 +f 765 766 84 +f 761 765 86 +f 762 761 89 +f 974 158 159 +f 134 793 135 +f 157 147 975 +f 973 971 157 +f 143 152 144 +f 156 974 135 +f 757 137 151 +f 137 967 789 +f 788 967 148 +f 787 794 132 +f 139 782 148 +f 970 782 146 +f 794 793 133 +f 155 149 973 +f 144 972 145 +f 131 786 132 +f 783 786 130 +f 784 783 129 +f 39 37 136 +f 919 992 921 +f 907 697 913 +f 303 988 922 +f 991 302 917 +f 301 990 916 +f 305 986 914 +f 38 304 909 +f 987 700 912 +f 297 995 699 +f 698 699 760 +f 108 107 963 +f 98 701 936 +f 35 103 30 +f 37 41 968 +f 93 993 92 +f 995 33 989 +f 758 32 760 +f 33 297 40 +f 95 31 759 +f 770 701 769 +f 769 702 768 +f 768 745 767 +f 754 755 792 +f 791 755 790 +f 790 756 151 +f 300 989 39 +f 874 934 980 +f 298 932 295 +f 698 32 985 +f 440 355 441 +f 441 451 442 +f 437 426 436 +f 438 416 437 +f 367 466 414 +f 476 471 475 +f 479 468 478 +f 480 454 479 +f 624 643 621 +f 418 462 626 +f 418 419 417 +f 417 368 411 +f 1322 436 428 +f 431 432 1121 +f 430 431 1308 +f 1323 356 440 +f 1319 420 1306 +f 1319 1306 422 +f 422 1305 423 +f 423 435 424 +f 1116 362 429 +f 419 1349 425 +f 432 433 1122 +f 434 1322 1321 +f 1115 410 1116 +f 1111 412 1115 +f 1111 1112 412 +f 1332 485 484 +f 1143 460 482 +f 1327 473 1333 +f 473 1329 483 +f 478 469 1330 +f 482 461 1332 +f 1107 477 463 +f 463 1139 1325 +f 465 1325 474 +f 1144 1137 459 +f 1132 465 1328 +f 1131 1132 472 +f 1144 459 1143 +f 475 481 1329 +f 476 1330 471 +f 1136 457 1137 +f 1133 456 1136 +f 1133 1134 456 +f 365 462 363 +f 1350 1348 1271 +f 1340 1047 1263 +f 629 1272 1344 +f 1347 1267 628 +f 627 1266 1346 +f 1342 631 1261 +f 364 1259 630 +f 625 1338 641 +f 1343 1262 1050 +f 623 1049 1351 +f 1048 1110 1049 +f 434 1321 433 +f 424 1294 1051 +f 429 361 1323 +f 363 1326 367 +f 626 1349 418 +f 360 359 1345 +f 1108 1110 358 +f 1341 623 366 +f 421 1109 357 +f 1052 1051 1119 +f 1095 1052 1118 +f 1094 1095 1117 +f 1104 1142 1105 +f 1105 1141 1106 +f 1106 1140 1107 +f 360 1345 365 +f 1224 1339 1292 +f 1295 1290 1256 +f 1048 1341 358 +f 902 904 1357 +f 982 1278 1003 +f 924 906 1282 +f 1253 903 1280 +f 622 296 1276 +f 1254 905 1297 +f 1099 749 1100 +f 1221 871 622 +f 1100 750 1222 +f 299 934 1277 +f 932 298 1290 +f 1293 935 1291 +f 1291 933 1290 +f 1002 981 1004 +g car-hatchback-blue_Cube.031_windows +usemtl windows +f 311 309 308 +f 312 313 307 +f 637 636 634 +f 633 639 638 +f 655 1273 1275 +f 656 923 1281 +f 310 311 308 +f 306 312 307 +f 635 637 634 +f 632 633 638 +f 654 655 1275 +f 1279 656 1281 +g car-hatchback-blue_Cube.031_lights +usemtl lights +f 275 276 278 +f 880 875 280 +f 883 881 282 +f 886 884 284 +f 887 286 285 +f 890 288 287 +f 882 893 885 +f 977 878 879 +f 894 978 289 +f 601 603 604 +f 1230 605 606 +f 1233 607 608 +f 1236 609 610 +f 611 612 1237 +f 613 614 1240 +f 1232 1243 616 +f 1229 1228 1335 +f 615 1336 1244 +f 277 275 278 +f 279 880 280 +f 281 883 282 +f 283 886 284 +f 889 887 285 +f 892 890 287 +f 895 894 289 +f 893 876 290 +f 888 885 891 +f 979 977 879 +f 876 877 290 +f 885 893 891 +f 893 882 876 +f 602 601 604 +f 1225 1230 606 +f 1231 1233 608 +f 1234 1236 610 +f 1239 611 1237 +f 1242 613 1240 +f 1232 616 1226 +f 1245 615 1244 +f 1243 1235 1241 +f 1243 1232 1235 +f 1235 1238 1241 +f 1337 1229 1335 +f 616 1227 1226 +g car-hatchback-blue_Cube.031_metal +usemtl metal +f 291 293 294 +f 859 858 251 +f 857 856 251 +f 866 865 251 +f 843 842 251 +f 840 839 251 +f 862 861 251 +f 839 866 251 +f 860 859 251 +f 858 857 251 +f 864 863 251 +f 856 843 251 +f 842 841 251 +f 841 840 251 +f 863 862 251 +f 865 864 251 +f 861 860 251 +f 851 852 267 +f 853 854 267 +f 845 844 267 +f 855 867 267 +f 869 870 267 +f 848 849 267 +f 870 845 267 +f 850 851 267 +f 852 853 267 +f 847 846 267 +f 854 855 267 +f 867 868 267 +f 868 869 267 +f 846 848 267 +f 844 847 267 +f 849 850 267 +f 620 619 617 +f 1209 577 1208 +f 1207 577 1206 +f 1216 577 1215 +f 1193 577 1192 +f 1190 577 1189 +f 1212 577 1211 +f 1189 577 1216 +f 1210 577 1209 +f 1208 577 1207 +f 1214 577 1213 +f 1206 577 1193 +f 1192 577 1191 +f 1191 577 1190 +f 1213 577 1212 +f 1215 577 1214 +f 1211 577 1210 +f 1201 593 1202 +f 1203 593 1204 +f 1195 593 1194 +f 1205 593 1217 +f 1219 593 1220 +f 1198 593 1199 +f 1220 593 1195 +f 1200 593 1201 +f 1202 593 1203 +f 1197 593 1196 +f 1204 593 1205 +f 1217 593 1218 +f 1218 593 1219 +f 1196 593 1198 +f 1194 593 1197 +f 1199 593 1200 +f 1251 1248 898 +f 896 1246 1250 +f 1249 1247 897 +f 292 291 294 +f 618 620 617 +f 901 1251 898 +f 900 896 1250 +f 899 1249 897 +g car-hatchback-blue_Cube.031_tires +usemtl tires +f 46 47 79 +f 48 49 77 +f 51 75 76 +f 52 53 123 +f 55 121 122 +f 43 44 82 +f 57 119 120 +f 45 46 80 +f 58 118 119 +f 45 81 82 +f 56 120 121 +f 54 122 123 +f 51 52 124 +f 50 76 77 +f 47 48 78 +f 58 43 83 +f 176 187 185 +f 171 181 180 +f 179 178 168 +f 167 177 193 +f 192 191 164 +f 163 190 189 +f 184 183 173 +f 188 187 176 +f 173 183 182 +f 189 188 117 +f 175 185 184 +f 191 190 163 +f 166 193 192 +f 178 177 167 +f 170 180 179 +f 172 182 181 +f 203 268 269 +f 197 262 263 +f 73 199 200 +f 224 234 233 +f 219 229 228 +f 227 226 216 +f 225 240 214 +f 239 238 212 +f 211 237 236 +f 232 231 221 +f 235 234 224 +f 221 231 230 +f 236 235 202 +f 223 233 232 +f 238 237 211 +f 214 240 239 +f 216 226 225 +f 218 228 227 +f 220 230 229 +f 201 266 268 +f 739 738 243 +f 704 252 253 +f 741 740 245 +f 751 748 247 +f 195 260 261 +f 705 253 254 +f 707 255 256 +f 59 241 259 +f 957 208 273 +f 735 257 258 +f 748 741 246 +f 201 725 265 +f 959 206 271 +f 738 737 242 +f 60 186 951 +f 71 70 196 +f 69 68 194 +f 66 209 956 +f 64 207 958 +f 62 205 960 +f 60 821 822 +f 73 72 727 +f 61 822 205 +f 74 200 951 +f 63 960 207 +f 66 65 958 +f 68 67 956 +f 70 69 954 +f 72 71 726 +f 710 809 808 +f 798 797 722 +f 709 795 810 +f 800 799 720 +f 717 802 801 +f 804 803 716 +f 806 805 714 +f 808 807 712 +f 722 797 796 +f 810 809 710 +f 720 799 798 +f 801 800 719 +f 803 802 717 +f 805 804 715 +f 712 807 806 +f 796 795 709 +f 204 269 270 +f 725 724 264 +f 703 250 252 +f 740 739 244 +f 703 753 249 +f 953 261 262 +f 724 198 263 +f 955 259 260 +f 706 254 255 +f 59 957 274 +f 734 256 257 +f 753 751 248 +f 208 959 272 +f 736 258 242 +f 206 976 270 +f 837 836 818 +f 826 825 730 +f 728 823 838 +f 828 827 732 +f 830 829 811 +f 832 831 813 +f 834 833 815 +f 836 835 817 +f 825 824 729 +f 838 837 819 +f 827 826 731 +f 829 828 733 +f 831 830 812 +f 815 833 832 +f 835 834 816 +f 824 823 728 +f 372 406 405 +f 374 404 403 +f 402 401 377 +f 378 450 449 +f 448 447 381 +f 369 409 408 +f 446 445 383 +f 371 407 406 +f 445 444 384 +f 408 407 371 +f 447 446 382 +f 449 448 380 +f 401 450 378 +f 403 402 376 +f 373 405 404 +f 384 444 409 +f 502 501 511 +f 497 496 506 +f 494 504 505 +f 493 492 519 +f 490 517 518 +f 488 515 516 +f 499 509 510 +f 502 513 514 +f 499 498 508 +f 443 514 515 +f 501 500 510 +f 489 516 517 +f 492 491 518 +f 493 503 504 +f 496 495 505 +f 498 497 507 +f 530 595 594 +f 524 589 588 +f 526 525 399 +f 550 549 559 +f 545 544 554 +f 542 552 553 +f 540 566 551 +f 538 564 565 +f 536 562 563 +f 547 557 558 +f 550 560 561 +f 547 546 556 +f 536 528 561 +f 548 558 559 +f 537 563 564 +f 540 539 565 +f 541 551 552 +f 544 543 553 +f 546 545 555 +f 529 594 592 +f 570 569 1088 +f 579 578 1054 +f 572 571 1090 +f 574 573 1098 +f 587 586 521 +f 580 579 1055 +f 1084 582 581 +f 585 567 385 +f 600 599 534 +f 1086 584 583 +f 573 572 1091 +f 592 591 1075 +f 598 597 532 +f 569 568 1087 +f 386 1171 1309 +f 397 1076 522 +f 395 1312 520 +f 1314 535 392 +f 1316 533 390 +f 389 1318 531 +f 1172 1171 386 +f 399 525 1077 +f 388 531 1172 +f 512 1309 526 +f 533 1318 389 +f 392 535 1316 +f 520 1314 393 +f 396 522 1312 +f 398 1077 1076 +f 1060 1061 1158 +f 1071 1072 1147 +f 1058 1160 1145 +f 1069 1070 1149 +f 1068 1151 1152 +f 1066 1153 1154 +f 1063 1064 1155 +f 1062 1157 1158 +f 1073 1146 1147 +f 1060 1159 1160 +f 1071 1148 1149 +f 1068 1069 1150 +f 1067 1152 1153 +f 1065 1154 1155 +f 1063 1156 1157 +f 1059 1145 1146 +f 596 595 530 +f 1075 591 590 +f 1054 578 576 +f 571 570 1089 +f 1053 576 575 +f 588 587 1311 +f 1074 590 589 +f 521 586 585 +f 1057 581 580 +f 385 567 600 +f 583 582 1084 +f 1103 575 574 +f 599 598 1317 +f 568 584 1086 +f 597 596 1334 +f 1168 1186 1187 +f 1081 1080 1175 +f 1170 1188 1173 +f 1083 1082 1177 +f 1162 1161 1179 +f 1163 1181 1182 +f 1165 1183 1184 +f 1167 1185 1186 +f 1079 1174 1175 +f 1169 1187 1188 +f 1081 1176 1177 +f 1161 1083 1178 +f 1162 1180 1181 +f 1164 1182 1183 +f 1166 1184 1185 +f 1079 1078 1173 +f 80 46 79 +f 78 48 77 +f 50 51 76 +f 124 52 123 +f 54 55 122 +f 83 43 82 +f 56 57 120 +f 81 45 80 +f 57 58 119 +f 44 45 82 +f 55 56 121 +f 53 54 123 +f 75 51 124 +f 49 50 77 +f 79 47 78 +f 118 58 83 +f 175 176 185 +f 170 171 180 +f 169 179 168 +f 166 167 193 +f 165 192 164 +f 162 163 189 +f 174 184 173 +f 117 188 176 +f 172 173 182 +f 162 189 117 +f 174 175 184 +f 164 191 163 +f 165 166 192 +f 168 178 167 +f 169 170 179 +f 171 172 181 +f 204 203 269 +f 198 197 263 +f 74 73 200 +f 223 224 233 +f 218 219 228 +f 217 227 216 +f 215 225 214 +f 213 239 212 +f 210 211 236 +f 222 232 221 +f 202 235 224 +f 220 221 230 +f 210 236 202 +f 222 223 232 +f 212 238 211 +f 213 214 239 +f 215 216 225 +f 217 218 227 +f 219 220 229 +f 203 201 268 +f 244 739 243 +f 705 704 253 +f 246 741 245 +f 248 751 247 +f 953 195 261 +f 706 705 254 +f 734 707 256 +f 955 59 259 +f 274 957 273 +f 736 735 258 +f 247 748 246 +f 266 201 265 +f 272 959 271 +f 243 738 242 +f 821 60 951 +f 726 71 196 +f 954 69 194 +f 67 66 956 +f 65 64 958 +f 63 62 960 +f 61 60 822 +f 199 73 727 +f 62 61 205 +f 186 74 951 +f 64 63 207 +f 209 66 958 +f 194 68 956 +f 196 70 954 +f 727 72 726 +f 711 710 808 +f 721 798 722 +f 708 709 810 +f 719 800 720 +f 718 717 801 +f 715 804 716 +f 713 806 714 +f 711 808 712 +f 723 722 796 +f 708 810 710 +f 721 720 798 +f 718 801 719 +f 716 803 717 +f 714 805 715 +f 713 712 806 +f 723 796 709 +f 976 204 270 +f 265 725 264 +f 704 703 252 +f 245 740 244 +f 250 703 249 +f 197 953 262 +f 264 724 263 +f 195 955 260 +f 707 706 255 +f 241 59 274 +f 735 734 257 +f 249 753 248 +f 273 208 272 +f 737 736 242 +f 271 206 270 +f 819 837 818 +f 731 826 730 +f 820 728 838 +f 733 828 732 +f 812 830 811 +f 814 832 813 +f 816 834 815 +f 818 836 817 +f 730 825 729 +f 820 838 819 +f 732 827 731 +f 811 829 733 +f 813 831 812 +f 814 815 832 +f 817 835 816 +f 729 824 728 +f 373 372 405 +f 375 374 403 +f 376 402 377 +f 379 378 449 +f 380 448 381 +f 370 369 408 +f 382 446 383 +f 372 371 406 +f 383 445 384 +f 370 408 371 +f 381 447 382 +f 379 449 380 +f 377 401 378 +f 375 403 376 +f 374 373 404 +f 369 384 409 +f 513 502 511 +f 507 497 506 +f 495 494 505 +f 503 493 519 +f 491 490 518 +f 489 488 516 +f 500 499 510 +f 443 502 514 +f 509 499 508 +f 488 443 515 +f 511 501 510 +f 490 489 517 +f 519 492 518 +f 494 493 504 +f 506 496 505 +f 508 498 507 +f 529 530 594 +f 523 524 588 +f 400 526 399 +f 560 550 559 +f 555 545 554 +f 543 542 553 +f 541 540 551 +f 539 538 565 +f 537 536 563 +f 548 547 558 +f 528 550 561 +f 557 547 556 +f 562 536 561 +f 549 548 559 +f 538 537 564 +f 566 540 565 +f 542 541 552 +f 554 544 553 +f 556 546 555 +f 527 529 592 +f 1089 570 1088 +f 1055 579 1054 +f 1091 572 1090 +f 1101 574 1098 +f 1311 587 521 +f 1056 580 1055 +f 1057 1084 581 +f 1313 585 385 +f 1315 600 534 +f 1085 1086 583 +f 1098 573 1091 +f 527 592 1075 +f 1317 598 532 +f 1088 569 1087 +f 512 386 1309 +f 396 397 522 +f 394 395 520 +f 393 1314 392 +f 391 1316 390 +f 388 389 531 +f 387 1172 386 +f 398 399 1077 +f 387 388 1172 +f 400 512 526 +f 390 533 389 +f 391 392 1316 +f 394 520 393 +f 395 396 1312 +f 397 398 1076 +f 1159 1060 1158 +f 1148 1071 1147 +f 1059 1058 1145 +f 1150 1069 1149 +f 1067 1068 1152 +f 1065 1066 1154 +f 1156 1063 1155 +f 1061 1062 1158 +f 1072 1073 1147 +f 1058 1060 1160 +f 1070 1071 1149 +f 1151 1068 1150 +f 1066 1067 1153 +f 1064 1065 1155 +f 1062 1063 1157 +f 1073 1059 1146 +f 1334 596 530 +f 1074 1075 590 +f 1053 1054 576 +f 1090 571 1089 +f 1103 1053 575 +f 523 588 1311 +f 524 1074 589 +f 1313 521 585 +f 1056 1057 580 +f 1315 385 600 +f 1085 583 1084 +f 1101 1103 574 +f 534 599 1317 +f 1087 568 1086 +f 532 597 1334 +f 1169 1168 1187 +f 1176 1081 1175 +f 1078 1170 1173 +f 1178 1083 1177 +f 1180 1162 1179 +f 1164 1163 1182 +f 1166 1165 1184 +f 1168 1167 1186 +f 1080 1079 1175 +f 1170 1169 1188 +f 1082 1081 1177 +f 1179 1161 1178 +f 1163 1162 1181 +f 1165 1164 1183 +f 1167 1166 1185 +f 1174 1079 1173 +g car-hatchback-blue_Cube.031_lights.red +usemtl lights.red +f 9 10 8 +f 5 672 670 +f 668 671 2 +f 4 659 673 +f 674 3 2 +f 1006 661 660 +f 1005 667 669 +f 12 679 662 +f 334 336 335 +f 331 332 1020 +f 328 1021 1018 +f 1023 1009 330 +f 1024 1021 328 +f 1010 1011 1360 +f 1359 1028 1019 +f 1012 1029 338 +f 7 9 8 +f 6 5 670 +f 1 668 2 +f 675 4 673 +f 671 674 2 +f 952 1006 660 +f 678 1005 669 +f 11 12 662 +f 333 334 335 +f 1022 331 1020 +f 327 328 1018 +f 1025 1023 330 +f 329 1024 328 +f 1310 1010 1360 +f 1017 1359 1019 +f 337 1012 338 +g car-hatchback-blue_Cube.031_lights.orange +usemtl lights.orange +f 13 15 16 +f 321 319 323 +f 677 17 18 +f 682 19 20 +f 685 21 22 +f 23 24 686 +f 25 26 689 +f 665 664 945 +f 676 666 28 +f 946 944 692 +f 681 684 664 +f 693 945 690 +f 687 690 684 +f 664 684 945 +f 690 945 684 +f 322 320 926 +f 999 318 317 +f 998 315 929 +f 325 326 940 +f 1007 1000 928 +f 1008 943 996 +f 997 323 319 +f 339 340 342 +f 652 648 650 +f 1027 1013 344 +f 1032 1030 346 +f 1035 1033 348 +f 1036 350 349 +f 1039 352 351 +f 1015 1303 1014 +f 354 1016 1026 +f 1042 1302 1304 +f 1031 1014 1034 +f 1043 1040 1303 +f 1037 1034 1040 +f 1014 1303 1034 +f 1040 1034 1303 +f 651 1285 1284 +f 1355 1283 646 +f 1287 644 1354 +f 657 1300 1298 +f 1361 1299 1286 +f 1352 1301 1362 +f 648 652 1353 +f 14 13 16 +f 324 321 323 +f 663 677 18 +f 680 682 20 +f 683 685 22 +f 688 23 686 +f 691 25 689 +f 27 676 28 +f 694 946 692 +f 927 322 926 +f 925 999 317 +f 931 998 929 +f 942 325 940 +f 941 1007 928 +f 316 1008 996 +f 930 997 319 +f 341 339 342 +f 653 652 650 +f 343 1027 344 +f 345 1032 346 +f 347 1035 348 +f 1038 1036 349 +f 1041 1039 351 +f 353 354 1026 +f 1044 1042 1304 +f 649 651 1284 +f 647 1355 646 +f 1289 1287 1354 +f 658 657 1298 +f 1356 1361 1286 +f 645 1352 1362 +f 1288 648 1353 diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/draw-mode.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/draw-mode.ts new file mode 100644 index 0000000000..79ae96422b --- /dev/null +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/draw-mode.ts @@ -0,0 +1,178 @@ +import { createTrackOverlay } from './overlay.ts'; + +const MAX_CONTROL_POINTS = 512; + +type DrawModeControllerOptions = { + canvas: HTMLCanvasElement; + onEnter?: () => void; + onExit?: () => void; + onPreviewTrack: (points: Float32Array) => void; + onClearPreview?: () => void; +}; + +export function createDrawModeController({ + canvas, + onEnter, + onExit, + onPreviewTrack, + onClearPreview, +}: DrawModeControllerOptions) { + const overlay = createTrackOverlay(canvas); + const controlPoints = new Float32Array(MAX_CONTROL_POINTS * 2); + + let active = false; + let pointCount = 0; + let dragIndex: number | null = null; + + function currentPoints() { + return controlPoints.subarray(0, pointCount * 2); + } + + function render() { + overlay.render(controlPoints, pointCount, dragIndex); + } + + function clearPoints() { + pointCount = 0; + dragIndex = null; + } + + function refreshPreview() { + if (!active) { + return; + } + render(); + if (pointCount < 4) { + return; + } + onPreviewTrack(currentPoints()); + } + + function enter() { + if (active) { + return; + } + active = true; + clearPoints(); + onEnter?.(); + onClearPreview?.(); + canvas.style.cursor = 'crosshair'; + overlay.show(); + render(); + } + + function exit() { + if (!active) { + return; + } + active = false; + dragIndex = null; + canvas.style.cursor = ''; + overlay.hide(); + onExit?.(); + } + + function confirm() { + if (!active || pointCount < 4) { + return null; + } + return new Float32Array(currentPoints()); + } + + function handleAspectChange() { + if (!active) { + return; + } + clearPoints(); + onClearPreview?.(); + render(); + } + + const handleMouseDown = (event: MouseEvent) => { + if (!active || event.button !== 0) { + return; + } + event.preventDefault(); + const [trackX, trackY] = overlay.clientToTrack(event.clientX, event.clientY); + const hitIndex = overlay.findNearest(controlPoints, pointCount, trackX, trackY); + if (hitIndex !== null) { + dragIndex = hitIndex; + canvas.style.cursor = 'grabbing'; + return; + } + if (pointCount >= MAX_CONTROL_POINTS) { + return; + } + controlPoints[pointCount * 2] = trackX; + controlPoints[pointCount * 2 + 1] = trackY; + pointCount++; + dragIndex = null; + refreshPreview(); + }; + + const handleMouseMove = (event: MouseEvent) => { + if (!active) { + return; + } + const [trackX, trackY] = overlay.clientToTrack(event.clientX, event.clientY); + if (dragIndex !== null) { + controlPoints[dragIndex * 2] = trackX; + controlPoints[dragIndex * 2 + 1] = trackY; + refreshPreview(); + return; + } + canvas.style.cursor = + overlay.findNearest(controlPoints, pointCount, trackX, trackY) !== null + ? 'grab' + : 'crosshair'; + }; + + const handleMouseUp = (event: MouseEvent) => { + if (!active || event.button !== 0) { + return; + } + dragIndex = null; + canvas.style.cursor = 'crosshair'; + }; + + const handleContextMenu = (event: MouseEvent) => { + if (!active) { + return; + } + event.preventDefault(); + if (pointCount === 0) { + return; + } + pointCount--; + refreshPreview(); + }; + + canvas.addEventListener('mousedown', handleMouseDown); + canvas.addEventListener('mousemove', handleMouseMove); + canvas.addEventListener('mouseup', handleMouseUp); + canvas.addEventListener('contextmenu', handleContextMenu); + + return { + get active() { + return active; + }, + + get pointCount() { + return pointCount; + }, + + enter, + exit, + confirm, + refreshPreview, + handleAspectChange, + + destroy() { + canvas.removeEventListener('mousedown', handleMouseDown); + canvas.removeEventListener('mousemove', handleMouseMove); + canvas.removeEventListener('mouseup', handleMouseUp); + canvas.removeEventListener('contextmenu', handleContextMenu); + overlay.destroy(); + }, + }; +} diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts index 95f4e5f452..3ad7155a22 100644 --- a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/ga.ts @@ -55,7 +55,6 @@ export const SimParams = d.struct({ mutationRate: d.f32, mutationStrength: d.f32, carSize: d.f32, - trackScale: d.f32, trackLength: d.f32, spawnX: d.f32, spawnY: d.f32, @@ -65,7 +64,6 @@ export const SimParams = d.struct({ export const CarStateArray = d.arrayOf(CarState, MAX_POP); export const GenomeArray = d.arrayOf(Genome, MAX_POP); -export const CarStateLayout = d.arrayOf(CarState); export const paramsAccess = tgpu.accessor(SimParams); @@ -98,7 +96,7 @@ const randSignedMat4x4 = () => { const makeSpawnState = () => { 'use gpu'; - const spawn = d.vec2f(paramsAccess.$.spawnX, paramsAccess.$.spawnY) * paramsAccess.$.trackScale; + const spawn = d.vec2f(paramsAccess.$.spawnX, paramsAccess.$.spawnY); return CarState({ position: spawn, angle: paramsAccess.$.spawnAngle, @@ -196,14 +194,14 @@ const initShader = (i: number) => { randf.seed2(d.vec2f(d.f32(i) + 1, paramsAccess.$.generation + 11)); initLayout.$.genome[i] = Genome({ - h1: InputLayer({ + h1: { wA: randSignedMat4x4(), wB: randSignedMat4x4(), wC: randSignedMat4x4(), bias: d.vec4f(), - }), - h2: DenseLayer({ w: randSignedMat4x4(), bias: d.vec4f() }), - out: OutputLayer({ steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }), + }, + h2: { w: randSignedMat4x4(), bias: d.vec4f() }, + out: { steer: randSignedVec4(), throttle: randSignedVec4(), bias: d.vec2f() }, }); initLayout.$.state[i] = makeSpawnState(); }; @@ -279,11 +277,11 @@ export function createGeneticPopulation(root: TgpuRoot, params: TgpuUniform { - 'use gpu'; - return p / params.$.trackScale; -}; +let depthTexture = root['~unstable'] + .createTexture({ size: [canvas.width, canvas.height], format: 'depth24plus' }) + .$usage('render'); const toTrackUV = (p: d.v2f) => { 'use gpu'; - const uvBase = (toTrackSpace(p) + 1) * 0.5; - return d.vec2f(uvBase.x, 1 - uvBase.y); + return d.vec2f((p.x / params.$.aspect) * 0.5 + 0.5, 0.5 - p.y * 0.5); }; const sampleTrack = (p: d.v2f, sampler: d.sampler) => { 'use gpu'; - const sample = std.textureSampleLevel(trackView.$, sampler, toTrackUV(p), 0); - return d.vec3f(sample.xy * 2 - 1, sample.z); + const uv = toTrackUV(p); + const inBounds = uv.x >= 0 && uv.x <= 1 && uv.y >= 0 && uv.y <= 1; + const sample = std.textureSampleLevel(trackView.$, sampler, uv, 0); + return d.vec3f(sample.xy * 2 - 1, std.select(0, sample.z, inBounds)); +}; + +const sampleTrackFrag = (p: d.v2f, sampler: d.sampler) => { + 'use gpu'; + const uv = toTrackUV(p); + const inBounds = uv.x >= 0 && uv.x <= 1 && uv.y >= 0 && uv.y <= 1; + const sample = std.textureSample(trackView.$, sampler, uv); + return d.vec3f(sample.xy * 2 - 1, std.select(d.f32(0), sample.z, inBounds)); }; const rotate = (v: d.v2f, angle: number) => { @@ -230,7 +251,7 @@ const simulatePipeline = root.createGuardedComputePipeline((i) => { const alive = std.select(d.u32(0), d.u32(1), onTrack); const forward = std.dot(dir, trackEnd.xy); - const lapLength = params.$.trackLength * params.$.trackScale; + const lapLength = params.$.trackLength; curPosition = std.select(curPosition, position, onTrack); curAngle = std.select(curAngle, angle, onTrack); @@ -278,6 +299,20 @@ const reductionBindGroups = [0, 1].map((i) => }), ); +const followEnabledBuffer = root.createBuffer(d.u32, 0).$usage('uniform'); + +const rayMarchLayout = tgpu.bindGroupLayout({ + carState: { storage: CarStateArray }, + followEnabled: { uniform: d.u32 }, +}); + +const rayMarchBindGroups = [0, 1].map((i) => + root.createBindGroup(rayMarchLayout, { + carState: ga.stateBuffers[i], + followEnabled: followEnabledBuffer, + }), +); + const reductionPipeline = root.createGuardedComputePipeline((i) => { 'use gpu'; if (d.u32(i) >= params.$.population) { @@ -309,7 +344,7 @@ const trackFragment = tgpu.fragmentFn({ })(({ uv }) => { 'use gpu'; const p = d.vec2f((uv.x * 2 - 1) * params.$.aspect, 1 - uv.y * 2); - const sample = sampleTrack(p, linearSampler.$); + const sample = sampleTrackFrag(p, linearSampler.$); const mask = sample.z; const color = std.mix(colors.grass.$, colors.road.$, mask); @@ -366,7 +401,7 @@ const carFragment = tgpu.fragmentFn({ return d.vec4f(rgb * a, a); }); -const instanceLayout = tgpu.vertexLayout(CarStateLayout, 'instance'); +const instanceLayout = tgpu.vertexLayout(d.arrayOf(CarState), 'instance'); const carPipeline = root.createRenderPipeline({ attribs: { @@ -387,30 +422,274 @@ const carPipeline = root.createRenderPipeline({ }, }); +const rayMarchFragment = tgpu.fragmentFn({ + in: { uv: d.vec2f }, + out: { color: d.vec4f, fragDepth: d.builtin.fragDepth }, +})(({ uv }) => { + 'use gpu'; + const carPos = rayMarchLayout.$.carState[0].position; + const followOffset = std.select( + d.vec3f(0, 0, 0), + d.vec3f(carPos.x, 0, carPos.y), + rayMarchLayout.$.followEnabled === 1, + ); + const ndc = (uv * 2 - 1) * d.vec2f(1, -1); + const farView = cameraUniform.$.projectionInverse * d.vec4f(ndc, 1, 1); + const farWorld = cameraUniform.$.viewInverse * d.vec4f(farView.xyz / farView.w, 1); + const orbitCamPos = cameraUniform.$.position.xyz; + const rd = std.normalize(farWorld.xyz - orbitCamPos); + const ro = orbitCamPos + followOffset; + + const safeRdY = std.select(d.f32(1), rd.y, std.abs(rd.y) > 0.001); + const tGround = -ro.y / safeRdY; + const intersects = std.abs(rd.y) > 0.001 && tGround > 0; + + const hitP = ro + rd * std.select(d.f32(0), tGround, intersects); + const trackMask = sampleTrackFrag(d.vec2f(hitP.x, hitP.z), linearSampler.$).z; + + // From below, only road is opaque; grass is transparent → sky + const hit = intersects && (ro.y >= 0 || trackMask > 0.5); + const dO = std.select(d.f32(0), tGround, hit); + + const surfColor = + std.mix(colors.grass.$, colors.road.$, trackMask) + + colors.paint.$ * (1 - std.smoothstep(0.6, 0.95, trackMask)) * trackMask; + // Flat ground normal, flipped for below-view + const N = std.select(d.vec3f(0, -1, 0), d.vec3f(0, 1, 0), ro.y > 0); + const lightDir = std.normalize(d.vec3f(0.5, 2.0, 0.8)); + const lit = surfColor * (0.2 + std.max(std.dot(N, lightDir), d.f32(0)) * 0.8); + const sky = d.vec3f(0.07, 0.09, 0.13); + const fog = std.clamp(dO / d.f32(20), 0, 1); + + const hitOrbit = hitP - followOffset; + const clipPos = cameraUniform.$.projection * cameraUniform.$.view * d.vec4f(hitOrbit, 1); + const depth = clipPos.z / clipPos.w; + + return { + color: d.vec4f(std.select(sky, std.mix(lit, sky, fog * fog), hit), 1), + fragDepth: std.select(d.f32(1), depth, hit), + }; +}); + +const rayMarchPipeline = root.createRenderPipeline({ + vertex: common.fullScreenTriangle, + fragment: rayMarchFragment, + targets: { color: { format: presentationFormat } }, + depthStencil: { format: 'depth24plus', depthWriteEnabled: true, depthCompare: 'always' }, +}); + +const carModelVertex = tgpu.vertexFn({ + in: { + modelPosition: d.vec3f, + modelNormal: d.vec3f, + position: d.vec2f, + angle: d.f32, + alive: d.u32, + progress: d.f32, + }, + out: { pos: d.builtin.position, normal: d.vec3f, progress: d.f32 }, +})((input) => { + 'use gpu'; + // Scale so car half-length = carSize + const scale = params.$.carSize / MODEL_HALF_LENGTH; + // Center model along its Z axis (front/back), y bottom is already at 0 + const centered = d.vec3f( + input.modelPosition.x, + input.modelPosition.y, + input.modelPosition.z - MODEL_Z_CENTER, + ); + const scaled = centered * scale; + const cosA = std.cos(input.angle); + const sinA = std.sin(input.angle); + const rotated = d.vec3f( + scaled.z * cosA + scaled.x * sinA, + scaled.y, + scaled.z * sinA - scaled.x * cosA, + ); + const carPos = rayMarchLayout.$.carState[0].position; + const followOffset = std.select( + d.vec3f(), + d.vec3f(carPos.x, 0, carPos.y), + rayMarchLayout.$.followEnabled === 1, + ); + const world = d.vec3f( + rotated.x + input.position.x - followOffset.x, + rotated.y + 0.001, + rotated.z + input.position.y - followOffset.z, + ); + const vp = cameraUniform.$.projection * cameraUniform.$.view * d.vec4f(world, 1); + const rotNormal = d.vec3f( + input.modelNormal.z * cosA + input.modelNormal.x * sinA, + input.modelNormal.y, + input.modelNormal.z * sinA - input.modelNormal.x * cosA, + ); + return { + pos: std.select(d.vec4f(), vp, input.alive === 1), + normal: rotNormal, + progress: input.progress, + }; +}); + +const carModelFragment = tgpu.fragmentFn({ + in: { normal: d.vec3f, progress: d.f32 }, + out: d.vec4f, +})(({ normal, progress }) => { + 'use gpu'; + const t = std.smoothstep(0, 1, progress); + const baseColor = std.mix(d.vec3f(0.4, 0.6, 1.0), d.vec3f(1.0, 0.85, 0.15), t); + const lightDir = std.normalize(d.vec3f(0.5, 2.0, 0.8)); + const diff = std.max(std.dot(normal, lightDir), 0); + return d.vec4f(baseColor * (0.2 + diff * 0.8), 1); +}); + +const carModelPipeline = root.createRenderPipeline({ + attribs: { + modelPosition: carModelVertexLayout.attrib.modelPosition, + modelNormal: carModelVertexLayout.attrib.modelNormal, + position: instanceLayout.attrib.position, + angle: instanceLayout.attrib.angle, + alive: instanceLayout.attrib.alive, + progress: instanceLayout.attrib.progress, + }, + vertex: carModelVertex, + fragment: carModelFragment, + primitive: { topology: 'triangle-list' }, + depthStencil: { format: 'depth24plus', depthWriteEnabled: true, depthCompare: 'less' }, + targets: { format: presentationFormat }, +}); + let steps = 0; let stepsPerFrame = STEPS_PER_DISPATCH; let stepsPerGeneration = 2048; let paused = false; let lastAspect = 1; +let lastCanvasWidth = canvas.width; +let lastCanvasHeight = canvas.height; let population = DEFAULT_POP; let rafHandle = 0; let pendingEvolve = false; let showBestOnly = false; -let hasChampion = false; let displayedBestFitness = 0; -const statsDiv = document.querySelector('.stats') as HTMLDivElement; +let is3DMode = false; +let cleanupOrbitCamera: (() => void) | null = null; +let followCamPos = d.vec4f(0, 0.8, 2, 1); +let followCamTarget = d.vec4f(0, 0, 0, 1); -function updateAspect() { - if (!canvas.width || !canvas.height) { +function getAspect(): number { + return canvas.width && canvas.height ? canvas.width / canvas.height : 1; +} + +function activateOrbitCamera() { + const { cleanupCamera } = setupOrbitCamera( + canvas, + { initPos: d.vec4f(followCamPos), target: d.vec4f(followCamTarget), minZoom: 0.3, maxZoom: 8 }, + (u) => { + cameraUniform.writePartial(u); + if (u.position !== undefined) { + followCamPos = u.position; + } + if (u.targetPos !== undefined) { + followCamTarget = u.targetPos; + } + }, + ); + cleanupOrbitCamera = cleanupCamera; +} + +/** Write an all-grass (mask = 0) texture so the old track disappears immediately. */ +function writeBlankTrackTexture() { + const size = 512; + const data = new Uint8ClampedArray(size * size * 4); + for (let i = 0; i < data.length; i += 4) { + data[i] = 128; // neutral dir x + data[i + 1] = 128; // neutral dir y + data[i + 2] = 0; // mask = 0 → off-track + data[i + 3] = 255; + } + trackTexture.write(new ImageData(data, size, size)); + trackTexture.generateMipmaps(); +} + +const trackState = createTrackState({ + baseCar: BASE_CAR, + applyTrack, + applyCarParams: (nextCarParams) => { + params.writePartial(nextCarParams); + }, +}); + +const drawMode = createDrawModeController({ + canvas, + onEnter: () => { + cleanupOrbitCamera?.(); + cleanupOrbitCamera = null; + paused = true; + }, + onExit: () => { + if (is3DMode) { + activateOrbitCamera(); + } + }, + onPreviewTrack: (points) => { + trackState.previewDrawnTrack(points, getAspect()); + }, + onClearPreview: writeBlankTrackTexture, +}); + +function enterDrawMode() { + drawMode.enter(); +} + +function confirmTrack() { + const drawnTrack = drawMode.confirm(); + if (!drawnTrack) { return; } - const nextAspect = canvas.width / canvas.height; - if (Math.abs(nextAspect - lastAspect) < 0.001) { + trackState.confirmDrawnTrack(drawnTrack, getAspect()); + drawMode.exit(); + paused = false; + startSimulation(); +} + +function cancelDraw() { + if (!drawMode.active) { return; } + drawMode.exit(); + trackState.applyCurrentGridTrack(getAspect()); + paused = false; + startSimulation(); +} + +const statsEl = document.querySelector('.stats'); +if (!statsEl) { + throw new Error('Missing .stats element'); +} +const stats = createStatsDisplay(statsEl); + +function updateAspect() { + if (!canvas.width || !canvas.height) return; + + if (canvas.width !== lastCanvasWidth || canvas.height !== lastCanvasHeight) { + lastCanvasWidth = canvas.width; + lastCanvasHeight = canvas.height; + depthTexture.destroy(); + depthTexture = root['~unstable'] + .createTexture({ size: [canvas.width, canvas.height], format: 'depth24plus' }) + .$usage('render'); + } + + const nextAspect = canvas.width / canvas.height; + if (Math.abs(nextAspect - lastAspect) < 0.001) return; lastAspect = nextAspect; params.writePartial({ aspect: nextAspect }); + + if (drawMode.active) { + drawMode.handleAspectChange(); + } else { + trackState.applyCurrentGridTrack(nextAspect); + } } function updatePopulation(nextPopulation: number) { @@ -423,80 +702,129 @@ function updatePopulation(nextPopulation: number) { ga.reinitCurrent(population); } -function frame() { - updateAspect(); +function updateFollowMode() { + followEnabledBuffer.write(is3DMode && showBestOnly ? 1 : 0); +} - if (!paused) { - if (pendingEvolve) { - ga.evolve(population); - if (hasChampion) { - const src = root.unwrap(championGenomeBuffer); - const encoder = root.device.createCommandEncoder(); - encoder.copyBufferToBuffer(src, 0, root.unwrap(ga.genomeBuffers[ga.current]), 0, src.size); - root.device.queue.submit([encoder.finish()]); - } - steps = 0; - params.writePartial({ generation: ga.generation }); - pendingEvolve = false; - } +function evolvePendingGeneration() { + if (!pendingEvolve) { + return; + } + ga.evolve(population); + const source = root.unwrap(championGenomeBuffer); + const encoder = root.device.createCommandEncoder(); + encoder.copyBufferToBuffer(source, 0, root.unwrap(ga.genomeBuffers[ga.current]), 0, source.size); + root.device.queue.submit([encoder.finish()]); + steps = 0; + params.writePartial({ generation: ga.generation }); + pendingEvolve = false; +} - const stepsToRun = Math.min(stepsPerFrame, stepsPerGeneration - steps); - if (stepsToRun <= 0) { - pendingEvolve = true; - } else { - const innerSteps = Math.min(stepsToRun, STEPS_PER_DISPATCH); - params.writePartial({ stepsPerDispatch: innerSteps }); - const dispatchCount = Math.ceil(stepsToRun / innerSteps); - - const simEncoder = root.device.createCommandEncoder(); - for (let dispatch = 0; dispatch < dispatchCount; dispatch++) { - simulatePipeline - .with(simBindGroups[ga.current]) - .with(simEncoder) - .dispatchThreads(population); - } - root.device.queue.submit([simEncoder.finish()]); +function runSimulationDispatches() { + if (steps >= stepsPerGeneration) { + return; + } + const stepsToRun = Math.min(stepsPerFrame, stepsPerGeneration - steps); + const innerSteps = Math.min(stepsToRun, STEPS_PER_DISPATCH); + params.writePartial({ stepsPerDispatch: innerSteps }); + const dispatchCount = Math.ceil(stepsToRun / innerSteps); + + const simEncoder = root.device.createCommandEncoder(); + for (let dispatch = 0; dispatch < dispatchCount; dispatch++) { + simulatePipeline.with(simBindGroups[ga.current]).with(simEncoder).dispatchThreads(population); + } + root.device.queue.submit([simEncoder.finish()]); - steps += dispatchCount * innerSteps; - } + steps += dispatchCount * innerSteps; +} - if (steps >= stepsPerGeneration) { - pendingEvolve = true; - ga.precomputeFitness(population); - const bg = reductionBindGroups[ga.current]; - - const reductionEncoder = root.device.createCommandEncoder(); - reductionEncoder.clearBuffer(root.unwrap(reductionPackedBuffer)); - reductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(population); - finalizeReductionPipeline.with(bg).with(reductionEncoder).dispatchThreads(1); - root.device.queue.submit([reductionEncoder.finish()]); - - hasChampion = true; - void bestFitnessBuffer.read().then((fitness) => { - displayedBestFitness = fitness; - }); - } +function finalizeGenerationIfNeeded() { + if (steps < stepsPerGeneration || pendingEvolve) { + return; } + pendingEvolve = true; + ga.precomputeFitness(population); + const bindGroup = reductionBindGroups[ga.current]; + + const reductionEncoder = root.device.createCommandEncoder(); + reductionEncoder.clearBuffer(root.unwrap(reductionPackedBuffer)); + reductionPipeline.with(bindGroup).with(reductionEncoder).dispatchThreads(population); + finalizeReductionPipeline.with(bindGroup).with(reductionEncoder).dispatchThreads(1); + root.device.queue.submit([reductionEncoder.finish()]); + + void bestFitnessBuffer.read().then((fitness) => { + displayedBestFitness = fitness; + }); +} - const genStr = String(ga.generation).padStart(5); - const stepStr = String(steps).padStart(String(stepsPerGeneration).length); - const bestStr = displayedBestFitness.toFixed(2).padStart(6); - statsDiv.textContent = `Gen ${genStr} Step ${stepStr}/${stepsPerGeneration} Pop ${population} Best ${bestStr}`; +function advanceSimulationFrame() { + evolvePendingGeneration(); + runSimulationDispatches(); + finalizeGenerationIfNeeded(); +} - trackPipeline.withColorAttachment({ view: context, clearValue: [0.04, 0.05, 0.07, 1] }).draw(3); +function render3DFrame() { + rayMarchPipeline + .with(rayMarchBindGroups[ga.current]) + .withColorAttachment({ color: { view: context, clearValue: [0.07, 0.09, 0.13, 1] } }) + .withDepthStencilAttachment({ + view: depthTexture, + depthClearValue: 1, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }) + .draw(3); + carModelPipeline + .with(rayMarchBindGroups[ga.current]) + .with(carModelVertexLayout, carModel.vertexBuffer) + .with(instanceLayout, ga.currentStateBuffer) + .withColorAttachment({ view: context, loadOp: 'load' }) + .withDepthStencilAttachment({ + view: depthTexture, + depthLoadOp: 'load', + depthStoreOp: 'store', + }) + .draw(carModel.vertexCount, showBestOnly ? 1 : population); +} +function render2DFrame() { + trackPipeline.withColorAttachment({ view: context, clearValue: [0.04, 0.05, 0.07, 1] }).draw(3); + if (drawMode.active) { + return; + } carPipeline - .withColorAttachment({ view: context, loadOp: 'load', storeOp: 'store' }) + .withColorAttachment({ view: context, loadOp: 'load' }) .with(instanceLayout, ga.currentStateBuffer) - .draw(4, showBestOnly && hasChampion ? 1 : population); + .draw(4, showBestOnly ? 1 : population); +} + +function frame() { + updateAspect(); + + if (!paused) { + advanceSimulationFrame(); + } + + if (drawMode.active) { + stats.setDrawMode(drawMode.pointCount); + } else { + stats.setSimulation(ga.generation, steps, stepsPerGeneration, population, displayedBestFitness); + } + + if (is3DMode && !drawMode.active) { + render3DFrame(); + } else { + render2DFrame(); + } rafHandle = requestAnimationFrame(frame); } function applyTrack(result: TrackResult) { trackTexture.write( - new ImageData(new Uint8ClampedArray(result.data), result.width, result.height), + new ImageData(result.data as Uint8ClampedArray, result.width, result.height), ); + trackTexture.generateMipmaps(); params.writePartial({ spawnX: result.spawn.position[0], spawnY: result.spawn.position[1], @@ -505,32 +833,12 @@ function applyTrack(result: TrackResult) { }); } -function applyGridSize(W: number, H: number) { - const scale = 5 / Math.max(W, H); - params.writePartial( - Object.fromEntries( - Object.entries(BASE_SPATIAL_PARAMS).map(([k, v]) => [k, v * scale]), - ) as typeof BASE_SPATIAL_PARAMS, - ); -} - -const GRID_SIZES: Record = { - S: [5, 4], - M: [8, 6], - L: [10, 9], - XL: [14, 12], -}; - -let trackSeed = (Math.random() * 100_000) | 0; -let gridSizeKey = 'S'; - function startSimulation() { steps = 0; pendingEvolve = false; - hasChampion = false; displayedBestFitness = 0; params.writePartial({ generation: 0 }); - ga.init(); + ga.init(population); updateAspect(); updatePopulation(population); @@ -539,27 +847,43 @@ function startSimulation() { } function newTrack() { - trackSeed = (Math.random() * 100_000) | 0; - const [W, H] = GRID_SIZES[gridSizeKey]; - applyGridSize(W, H); - applyTrack(generateGridTrack(trackSeed, W, H)); + trackState.newTrack(getAspect()); startSimulation(); } -applyGridSize(...GRID_SIZES[gridSizeKey]); -applyTrack(generateGridTrack(trackSeed, ...GRID_SIZES[gridSizeKey])); +trackState.applyCurrentGridTrack(); startSimulation(); // #region Example controls & Cleanup export const controls = defineControls({ - 'New Track': { onButtonClick: newTrack }, + 'Random Track': { onButtonClick: newTrack }, + 'Draw New Track': { onButtonClick: enterDrawMode }, + 'Confirm Track': { onButtonClick: confirmTrack }, + 'Cancel Drawing': { onButtonClick: cancelDraw }, 'Grid size': { - initial: 'S', - options: ['S', 'M', 'L', 'XL'], + initial: trackState.gridSizeKey, + options: [...GRID_SIZE_KEYS], onSelectChange: (value: string) => { - gridSizeKey = value; - newTrack(); + trackState.setGridSize(value as GridSizeKey); + if (drawMode.active) { + drawMode.refreshPreview(); + } else { + newTrack(); + } + }, + }, + '3D View': { + initial: false, + onToggleChange: (value: boolean) => { + is3DMode = value; + updateFollowMode(); + if (value && !drawMode.active) { + activateOrbitCamera(); + } else { + cleanupOrbitCamera?.(); + cleanupOrbitCamera = null; + } }, }, Pause: { @@ -572,6 +896,7 @@ export const controls = defineControls({ initial: false, onToggleChange: (value: boolean) => { showBestOnly = value; + updateFollowMode(); }, }, 'Steps per frame': { @@ -623,6 +948,8 @@ export const controls = defineControls({ export function onCleanup() { cancelAnimationFrame(rafHandle); + cleanupOrbitCamera?.(); + drawMode.destroy(); root.destroy(); } diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/load-car-model.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/load-car-model.ts new file mode 100644 index 0000000000..9bad83cc52 --- /dev/null +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/load-car-model.ts @@ -0,0 +1,54 @@ +import { load } from '@loaders.gl/core'; +import { OBJLoader } from '@loaders.gl/obj'; +import tgpu, { d, type TgpuRoot } from 'typegpu'; + +export const carModelVertexLayout = tgpu.vertexLayout((n: number) => + d.arrayOf(d.struct({ modelPosition: d.vec3f, modelNormal: d.vec3f }), n), +); + +// Pre-computed from OBJ bounds +export const MODEL_Z_CENTER = (-0.8078 + 0.9979) / 2; +export const MODEL_HALF_LENGTH = (0.9979 - -0.8078) / 2; + +export async function loadCarModel(root: TgpuRoot) { + const mesh = await load('/TypeGPU/assets/genetic-car/car-hatchback-blue.obj', OBJLoader); + const positions = mesh.attributes.POSITION.value as Float32Array; + const vertexCount = positions.length / 3; + + const normals = new Float32Array(positions.length); + for (let i = 0; i < vertexCount; i += 3) { + const i0 = i * 3, + i1 = (i + 1) * 3, + i2 = (i + 2) * 3; + const e1x = positions[i1] - positions[i0]; + const e1y = positions[i1 + 1] - positions[i0 + 1]; + const e1z = positions[i1 + 2] - positions[i0 + 2]; + const e2x = positions[i2] - positions[i0]; + const e2y = positions[i2 + 1] - positions[i0 + 1]; + const e2z = positions[i2 + 2] - positions[i0 + 2]; + const nx = e1y * e2z - e1z * e2y; + const ny = e1z * e2x - e1x * e2z; + const nz = e1x * e2y - e1y * e2x; + const len = Math.hypot(nx, ny, nz) || 1; + for (let j = 0; j < 3; j++) { + normals[(i + j) * 3] = nx / len; + normals[(i + j) * 3 + 1] = ny / len; + normals[(i + j) * 3 + 2] = nz / len; + } + } + + const vertices = []; + for (let i = 0; i < vertexCount; i++) { + vertices.push({ + modelPosition: d.vec3f(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]), + modelNormal: d.vec3f(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]), + }); + } + + const vertexBuffer = root + .createBuffer(carModelVertexLayout.schemaForCount(vertexCount)) + .$usage('vertex'); + vertexBuffer.write(vertices); + + return { vertexBuffer, vertexCount }; +} diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/overlay.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/overlay.ts new file mode 100644 index 0000000000..12535bf7e6 --- /dev/null +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/overlay.ts @@ -0,0 +1,105 @@ +export function createTrackOverlay(canvas: HTMLCanvasElement) { + const el = document.createElement('canvas'); + el.style.cssText = + 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;display:none'; + canvas.insertAdjacentElement('afterend', el); + if (canvas.parentElement) canvas.parentElement.style.position = 'relative'; + const ctx = el.getContext('2d'); + if (!ctx) { + throw new Error('Failed to get 2D context for track overlay'); + } + + const aspect = () => (canvas.width && canvas.height ? canvas.width / canvas.height : 1); + const toPixel = (x: number, y: number): [number, number] => [ + ((x / aspect() + 1) / 2) * el.width, + ((1 - y) / 2) * el.height, + ]; + + return { + show() { + el.style.display = ''; + }, + hide() { + el.style.display = 'none'; + }, + + destroy() { + el.remove(); + }, + + clientToTrack(clientX: number, clientY: number): [number, number] { + const rect = canvas.getBoundingClientRect(); + return [ + (((clientX - rect.left) / rect.width) * 2 - 1) * aspect(), + 1 - ((clientY - rect.top) / rect.height) * 2, + ]; + }, + + findNearest(pts: Float32Array, n: number, x: number, y: number, hitPx = 15): number | null { + const rect = canvas.getBoundingClientRect(); + const w = rect.width || canvas.width, + h = rect.height || canvas.height; + const a = aspect(); + const px = ((x / a + 1) / 2) * w, + py = ((1 - y) / 2) * h; + let best: number | null = null, + bestD = hitPx * hitPx; + for (let i = 0; i < n; i++) { + const d2 = + (((pts[i * 2] / a + 1) / 2) * w - px) ** 2 + (((1 - pts[i * 2 + 1]) / 2) * h - py) ** 2; + if (d2 < bestD) { + bestD = d2; + best = i; + } + } + return best; + }, + + render(pts: Float32Array, n: number, dragIdx: number | null) { + el.width = canvas.width; + el.height = canvas.height; + ctx.clearRect(0, 0, el.width, el.height); + if (n === 0) return; + + if (n >= 2) { + ctx.strokeStyle = 'rgba(255, 210, 60, 0.75)'; + ctx.lineWidth = 1.5; + ctx.lineJoin = 'round'; + ctx.setLineDash([]); + ctx.beginPath(); + for (let i = 0; i < n; i++) { + const [x, y] = toPixel(pts[i * 2], pts[i * 2 + 1]); + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.stroke(); + + const [lx, ly] = toPixel(pts[(n - 1) * 2], pts[(n - 1) * 2 + 1]); + const [fx, fy] = toPixel(pts[0], pts[1]); + ctx.setLineDash([6, 6]); + ctx.strokeStyle = 'rgba(255, 210, 60, 0.35)'; + ctx.beginPath(); + ctx.moveTo(lx, ly); + ctx.lineTo(fx, fy); + ctx.stroke(); + ctx.setLineDash([]); + } + + for (let i = 0; i < n; i++) { + const [x, y] = toPixel(pts[i * 2], pts[i * 2 + 1]); + ctx.beginPath(); + ctx.arc(x, y, i === 0 ? 8 : 6, 0, Math.PI * 2); + ctx.fillStyle = + i === dragIdx + ? 'rgba(100,180,255,1)' + : i === 0 + ? 'rgba(60,255,120,1)' + : 'rgba(255,255,255,0.9)'; + ctx.fill(); + ctx.strokeStyle = 'rgba(0,0,0,0.55)'; + ctx.lineWidth = 1.5; + ctx.stroke(); + } + }, + }; +} diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/stats.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/stats.ts new file mode 100644 index 0000000000..4a5b622232 --- /dev/null +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/stats.ts @@ -0,0 +1,19 @@ +export function createStatsDisplay(el: HTMLElement) { + return { + setDrawMode(nodeCount: number) { + const need = 4 - nodeCount; + const ready = + nodeCount >= 4 ? '— ready to confirm!' : `— need ${need} more node${need === 1 ? '' : 's'}`; + el.textContent = + `Draw mode | Click: add node | Drag: move node | RMB: remove last | ` + + `${nodeCount} node${nodeCount === 1 ? '' : 's'} ${ready}`; + }, + + setSimulation(gen: number, step: number, stepsPerGen: number, pop: number, best: number) { + el.textContent = + `Gen ${String(gen).padStart(5)} ` + + `Step ${String(step).padStart(String(stepsPerGen).length)}/${stepsPerGen} ` + + `Pop ${pop} Best ${best.toFixed(2).padStart(6)}`; + }, + }; +} diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track-state.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track-state.ts new file mode 100644 index 0000000000..bfb6530f1a --- /dev/null +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track-state.ts @@ -0,0 +1,99 @@ +import { generateDrawnTrack, generateGridTrack, type TrackResult } from './track.ts'; + +export type BaseCarParams = { + maxSpeed: number; + accel: number; + turnRate: number; + drag: number; + sensorDistance: number; + carSize: number; +}; + +export const GRID_SIZES = { + S: [5, 4], + M: [8, 6], + L: [10, 9], + XL: [14, 12], +} as const; + +export const GRID_SIZE_KEYS = Object.keys(GRID_SIZES) as GridSizeKey[]; + +export type GridSizeKey = keyof typeof GRID_SIZES; + +type TrackStateOptions = { + baseCar: BaseCarParams; + applyTrack: (result: TrackResult) => void; + applyCarParams: (params: BaseCarParams) => void; + initialGridSizeKey?: GridSizeKey; +}; + +function scaleCarParams(baseCar: BaseCarParams, scale: number): BaseCarParams { + return { + maxSpeed: baseCar.maxSpeed * scale, + accel: baseCar.accel * scale, + turnRate: baseCar.turnRate * scale, + drag: baseCar.drag * scale, + sensorDistance: baseCar.sensorDistance * scale, + carSize: baseCar.carSize * scale, + }; +} + +export function createTrackState({ + baseCar, + applyTrack, + applyCarParams, + initialGridSizeKey = 'S', +}: TrackStateOptions) { + let trackSeed = (Math.random() * 100_000) | 0; + let gridSizeKey = initialGridSizeKey; + + function applyGridSize(width: number, height: number) { + const scale = 5 / Math.max(width, height); + applyCarParams(scaleCarParams(baseCar, scale)); + } + + function getGridSize() { + return GRID_SIZES[gridSizeKey]; + } + + function buildDrawnTrack(points: Float32Array, aspect: number): TrackResult { + const [width, height] = getGridSize(); + return generateDrawnTrack(points, 0.172 * (1.6 / Math.max(width, height)), aspect); + } + + function applyCurrentGridTrack(aspect = 1) { + const [width, height] = getGridSize(); + applyGridSize(width, height); + applyTrack(generateGridTrack(trackSeed, width, height, aspect)); + } + + function previewDrawnTrack(points: Float32Array, aspect: number) { + applyTrack(buildDrawnTrack(points, aspect)); + } + + function confirmDrawnTrack(points: Float32Array, aspect: number) { + const [width, height] = getGridSize(); + applyGridSize(width, height); + applyTrack(buildDrawnTrack(points, aspect)); + } + + function newTrack(aspect = 1) { + trackSeed = (Math.random() * 100_000) | 0; + applyCurrentGridTrack(aspect); + } + + return { + get gridSizeKey() { + return gridSizeKey; + }, + + setGridSize(nextGridSizeKey: GridSizeKey) { + gridSizeKey = nextGridSizeKey; + }, + + applyCurrentGridTrack, + previewDrawnTrack, + confirmDrawnTrack, + newTrack, + }; +} diff --git a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts index f569a0d2e2..937934c031 100644 --- a/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts +++ b/apps/typegpu-docs/src/examples/algorithms/genetic-racing/track.ts @@ -1,5 +1,3 @@ -import { std } from 'typegpu'; - export type TrackResult = { width: number; height: number; @@ -8,8 +6,6 @@ export type TrackResult = { trackLength: number; }; -type Pt = { x: number; y: number }; - function mulberry32(seed: number) { let t = seed >>> 0; return () => { @@ -21,224 +17,242 @@ function mulberry32(seed: number) { }; } -function findBestSpawnIndex(pts: Pt[], windowSize: number): number { - const n = pts.length; - const curv = new Float64Array(n); - for (let i = 0; i < n; i++) { - const prev = pts[(i - 1 + n) % n]; - const curr = pts[i]; - const next = pts[(i + 1) % n]; - - const t1x = curr.x - prev.x; - const t1y = curr.y - prev.y; - const t1len = Math.hypot(t1x, t1y) || 1; - - const t2x = next.x - curr.x; - const t2y = next.y - curr.y; - const t2len = Math.hypot(t2x, t2y) || 1; - - curv[i] = Math.abs((t1x / t1len) * (t2y / t2len) - (t1y / t1len) * (t2x / t2len)); - } - - let windowSum = 0; - for (let i = 0; i < windowSize; i++) windowSum += curv[i]; - - let minSum = windowSum; - let bestStart = 0; - - for (let i = 1; i < n; i++) { - windowSum += curv[(i + windowSize - 1) % n] - curv[i - 1]; - if (windowSum < minSum) { - minSum = windowSum; - bestStart = i; - } - } - - return (bestStart + Math.floor(windowSize / 2)) % n; -} - -function catmullRomResample(points: Pt[], numSamples: number): Pt[] { - const n = points.length; - const result: Pt[] = []; +/** + * Catmull-Rom spline resampling. + * @param points Interleaved Float32Array [x0,y0,x1,y1,…] of control points (closed loop). + * @returns Interleaved Float32Array of numSamples evenly-spaced points. + */ +function catmullRomResample(points: Float32Array, numSamples: number): Float32Array { + const n = points.length >> 1; + const result = new Float32Array(numSamples * 2); for (let s = 0; s < numSamples; s++) { const tTotal = s / numSamples; const seg = Math.floor(tTotal * n); const t = tTotal * n - seg; - const i0 = (seg - 1 + n) % n; - const i1 = seg; - const i2 = (seg + 1) % n; - const i3 = (seg + 2) % n; - - const p0 = points[i0]; - const p1 = points[i1]; - const p2 = points[i2]; - const p3 = points[i3]; - - const t2 = t * t; - const t3 = t2 * t; - - result.push({ - x: - 0.5 * - (2 * p1.x + - (-p0.x + p2.x) * t + - (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + - (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3), - y: - 0.5 * - (2 * p1.y + - (-p0.y + p2.y) * t + - (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + - (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3), - }); + const p0 = ((seg - 1 + n) % n) * 2; + const p1 = seg * 2; + const p2 = ((seg + 1) % n) * 2; + const p3 = ((seg + 2) % n) * 2; + + const t2 = t * t, + t3 = t2 * t; + + result[s * 2] = + 0.5 * + (2 * points[p1] + + (-points[p0] + points[p2]) * t + + (2 * points[p0] - 5 * points[p1] + 4 * points[p2] - points[p3]) * t2 + + (-points[p0] + 3 * points[p1] - 3 * points[p2] + points[p3]) * t3); + + result[s * 2 + 1] = + 0.5 * + (2 * points[p1 + 1] + + (-points[p0 + 1] + points[p2 + 1]) * t + + (2 * points[p0 + 1] - 5 * points[p1 + 1] + 4 * points[p2 + 1] - points[p3 + 1]) * t2 + + (-points[p0 + 1] + 3 * points[p1 + 1] - 3 * points[p2 + 1] + points[p3 + 1]) * t3); } return result; } +/** + * Chaikin corner-cutting — smooths a closed polygon over N iterations. + * @param pts Interleaved Float32Array [x0,y0,x1,y1,…]. + * @returns Interleaved Float32Array with 2^iterations × as many points. + */ +function chaikinSmooth(pts: Float32Array, iterations = 2): Float32Array { + let cur = pts; + for (let it = 0; it < iterations; it++) { + const n = cur.length >> 1; + const next = new Float32Array(n * 4); // each point becomes 2 → length doubles + for (let i = 0; i < n; i++) { + const ai = i * 2; + const bi = ((i + 1) % n) * 2; + next[i * 4] = 0.75 * cur[ai] + 0.25 * cur[bi]; + next[i * 4 + 1] = 0.75 * cur[ai + 1] + 0.25 * cur[bi + 1]; + next[i * 4 + 2] = 0.25 * cur[ai] + 0.75 * cur[bi]; + next[i * 4 + 3] = 0.25 * cur[ai + 1] + 0.75 * cur[bi + 1]; + } + cur = next; + } + return cur; +} + +/** + * Rasterizes a splined path into a track texture. + * Coordinate system: tx ∈ [-aspect, +aspect], ty ∈ [-1, +1]. + * Uses per-segment bounding boxes for efficiency. + * @param path Interleaved Float32Array [x0,y0,x1,y1,…]. + */ function buildTrackTexture( - splinedPath: Pt[], + path: Float32Array, textureSize: number, - trackHalfWidth: number, - spawnIdx?: number, + halfWidth: number, + aspect: number, ): TrackResult { - const feather = trackHalfWidth * 0.4; - - const segments = splinedPath.map((cell, idx) => { - const next = splinedPath[(idx + 1) % splinedPath.length]; - const dx = next.x - cell.x; - const dy = next.y - cell.y; - const len = Math.hypot(dx, dy) || 1; - return { - ax: cell.x, - ay: cell.y, - ux: dx, - uy: dy, - dx: dx / len, - dy: dy / len, - len, - len2: dx * dx + dy * dy, - }; - }); - + const feather = halfWidth * 0.4; + const extents = halfWidth + feather; + const sz = textureSize - 1; let totalLen = 0; - for (const segment of segments) { - totalLen += segment.len; - } - const data = new Uint8ClampedArray(textureSize * textureSize * 4); - - for (let y = 0; y < textureSize; y++) { - const ty = 1 - (y / (textureSize - 1)) * 2; - for (let x = 0; x < textureSize; x++) { - const tx = (x / (textureSize - 1)) * 2 - 1; - - let minSegDist = Infinity; - let dir = { x: 1, y: 0 }; - for (const segment of segments) { - const px = tx - segment.ax; - const py = ty - segment.ay; - const t = Math.max(0, Math.min(1, (px * segment.ux + py * segment.uy) / segment.len2)); - const cx = segment.ax + segment.ux * t; - const cy = segment.ay + segment.uy * t; - const dx = tx - cx; - const dy = ty - cy; - const dist = dx * dx + dy * dy; - if (dist < minSegDist) { - minSegDist = dist; - dir = { x: segment.dx, y: segment.dy }; + for (let i = 3; i < data.length; i += 4) data[i] = 255; + + const numPts = path.length >> 1; + for (let i = 0; i < numPts; i++) { + const ai = i * 2; + const bi = ((i + 1) % numPts) * 2; + + const ax = path[ai], + ay = path[ai + 1]; + const bx = path[bi], + by = path[bi + 1]; + const dx = bx - ax, + dy = by - ay; + const len = Math.hypot(dx, dy) || 1; + totalLen += len; + const ux = dx / len, + uy = dy / len, + len2 = dx * dx + dy * dy; + + const minPx = Math.max(0, Math.floor((((Math.min(ax, bx) - extents) / aspect + 1) / 2) * sz)); + const maxPx = Math.min(sz, Math.ceil((((Math.max(ax, bx) + extents) / aspect + 1) / 2) * sz)); + const minPy = Math.max(0, Math.floor(((1 - (Math.max(ay, by) + extents)) / 2) * sz)); + const maxPy = Math.min(sz, Math.ceil(((1 - (Math.min(ay, by) - extents)) / 2) * sz)); + + for (let py = minPy; py <= maxPy; py++) { + const ty = 1 - (py / sz) * 2; + for (let px = minPx; px <= maxPx; px++) { + const tx = ((px / sz) * 2 - 1) * aspect; + const t = Math.max(0, Math.min(1, ((tx - ax) * dx + (ty - ay) * dy) / len2)); + const dist = Math.hypot(tx - (ax + dx * t), ty - (ay + dy * t)); + if (dist > extents) continue; + const st = Math.max(0, Math.min(1, (dist - halfWidth) / feather)); + const mask = Math.round((1 - st * st * (3 - 2 * st)) * 255); + const idx = (py * textureSize + px) * 4; + if (mask > data[idx + 2]) { + data[idx] = Math.round((ux * 0.5 + 0.5) * 255); + data[idx + 1] = Math.round((uy * 0.5 + 0.5) * 255); + data[idx + 2] = mask; } } - - const dist = Math.sqrt(minSegDist); - const mask = 1 - std.smoothstep(trackHalfWidth, trackHalfWidth + feather, dist); - - const idx = (y * textureSize + x) * 4; - data[idx] = Math.round((dir.x * 0.5 + 0.5) * 255); - data[idx + 1] = Math.round((dir.y * 0.5 + 0.5) * 255); - data[idx + 2] = Math.round(mask * 255); - data[idx + 3] = 255; } } - const resolvedSpawnIdx = spawnIdx ?? findBestSpawnIndex(splinedPath, 25); - const spawnStart = splinedPath[resolvedSpawnIdx]; - const spawnNext = splinedPath[(resolvedSpawnIdx + 1) % splinedPath.length]; - const spawnAngle = Math.atan2(spawnNext.y - spawnStart.y, spawnNext.x - spawnStart.x); - return { width: textureSize, height: textureSize, data, spawn: { - position: [spawnStart.x, spawnStart.y], - angle: spawnAngle, + position: [path[0], path[1]], + angle: Math.atan2(path[3] - path[1], path[2] - path[0]), }, trackLength: totalLen, }; } -function generateLoopPath(W: number, H: number, rand: () => number): number[] { - const minLength = Math.max(8, Math.floor(W * H * 0.4)); - const path: number[] = [0]; - const inPath = new Map([[0, 0]]); +/** + * Random-walk loop path over a W×H grid. + * @returns A Uint16Array subarray view of the valid path. + */ +function generateLoopPath(W: number, H: number, rand: () => number): Uint16Array { + const maxCells = W * H; + const minLength = Math.max(8, Math.floor(maxCells * 0.4)); + + // Pre-allocate worst-case path length (every cell visited once) + const pathBuf = new Uint16Array(maxCells); + let pathLen = 1; + pathBuf[0] = 0; + + // Direct-address lookup: inPath[cell] = index in pathBuf, or -1 if absent + const inPath = new Int32Array(maxCells).fill(-1); + inPath[0] = 0; + + const neighbourBuf = new Uint16Array(4); for (let attempts = 0; attempts < 2_000_000; attempts++) { - const current = path[path.length - 1]; - const x = current % W, - y = Math.floor(current / W); + const current = pathBuf[pathLen - 1]; + const x = current % W; + const y = (current / W) | 0; - const neighbors: number[] = []; - if (x > 0) neighbors.push(current - 1); - if (x < W - 1) neighbors.push(current + 1); - if (y > 0) neighbors.push(current - W); - if (y < H - 1) neighbors.push(current + W); + let nc = 0; + if (x > 0) neighbourBuf[nc++] = current - 1; + if (x < W - 1) neighbourBuf[nc++] = current + 1; + if (y > 0) neighbourBuf[nc++] = current - W; + if (y < H - 1) neighbourBuf[nc++] = current + W; - const next = neighbors[Math.floor(rand() * neighbors.length)]; + const next = neighbourBuf[(rand() * nc) | 0]; - if (next === 0 && path.length >= minLength) { - return path; // closed loop found + if (next === 0 && pathLen >= minLength) { + return pathBuf.subarray(0, pathLen); } - const existingIdx = inPath.get(next); - if (existingIdx !== undefined) { - // Erase the loop back to where 'next' was first visited - for (let i = existingIdx + 1; i < path.length; i++) { - inPath.delete(path[i]); - } - path.length = existingIdx + 1; + const existingIdx = inPath[next]; + if (existingIdx !== -1) { + // Erase the loop: remove all cells after existingIdx from the index + for (let i = existingIdx + 1; i < pathLen; i++) inPath[pathBuf[i]] = -1; + pathLen = existingIdx + 1; } else { - inPath.set(next, path.length); - path.push(next); + inPath[next] = pathLen; + pathBuf[pathLen++] = next; } } - const perimeter: number[] = []; - for (let x = 0; x < W; x++) perimeter.push(x); - for (let y = 1; y < H; y++) perimeter.push(y * W + W - 1); - for (let x = W - 2; x >= 0; x--) perimeter.push((H - 1) * W + x); - for (let y = H - 2; y > 0; y--) perimeter.push(y * W); - return perimeter; + // Fallback: clockwise perimeter + const perim: number[] = []; + for (let px = 0; px < W; px++) perim.push(px); + for (let py = 1; py < H; py++) perim.push(py * W + W - 1); + for (let px = W - 2; px >= 0; px--) perim.push((H - 1) * W + px); + for (let py = H - 2; py > 0; py--) perim.push(py * W); + return new Uint16Array(perim); } -export function generateGridTrack(seed: number, W = 5, H = 4, textureSize = 512): TrackResult { +export function generateGridTrack( + seed: number, + W = 5, + H = 4, + aspect = 1, + textureSize = 512, +): TrackResult { const rand = mulberry32(seed); const cellPath = generateLoopPath(W, H, rand); + const n = cellPath.length; - const controlPoints = cellPath.map((cell) => { + // Build interleaved Float32Array of control points directly + const controlPoints = new Float32Array(n * 2); + for (let i = 0; i < n; i++) { + const cell = cellPath[i]; const col = cell % W, - row = Math.floor(cell / W); - const cx = -0.8 + ((col + 0.5) * 1.6) / W; + row = (cell / W) | 0; + const cx = (-0.8 + ((col + 0.5) * 1.6) / W) * aspect; const cy = 0.8 - ((row + 0.5) * 1.6) / H; - const jx = (rand() * 2 - 1) * 0.06 * (1.6 / W); - const jy = (rand() * 2 - 1) * 0.06 * (1.6 / H); - return { x: cx + jx, y: cy + jy }; - }); + controlPoints[i * 2] = cx + (rand() * 2 - 1) * 0.06 * (1.6 / W) * aspect; + controlPoints[i * 2 + 1] = cy + (rand() * 2 - 1) * 0.06 * (1.6 / H); + } const trackHalfWidth = 0.172 * (1.6 / Math.max(W, H)); - const numSamples = Math.max(120, cellPath.length * 6); - const splinedPath = catmullRomResample(controlPoints, numSamples); - return buildTrackTexture(splinedPath, textureSize, trackHalfWidth); + const numSamples = Math.max(120, n * 6); + return buildTrackTexture( + catmullRomResample(controlPoints, numSamples), + textureSize, + trackHalfWidth, + aspect, + ); +} + +export function generateDrawnTrack( + pts: Float32Array, + halfWidth: number, + aspect: number, + textureSize = 512, +): TrackResult { + const n = pts.length >> 1; + const numSamples = Math.max(120, n * 6); + return buildTrackTexture( + catmullRomResample(chaikinSmooth(pts), numSamples), + textureSize, + halfWidth, + aspect, + ); }