├── .gitignore ├── LICENSE ├── README.md ├── chp01_vectors ├── NOC_1_10_motion101_acceleration │ └── NOC_1_10_motion101_acceleration.rb ├── NOC_1_11_motion101_acceleration_array │ └── NOC_1_11_motion101_acceleration_array.rb ├── NOC_1_1_bouncingball_novectors │ └── NOC_1_1_bouncingball_novectors.rb ├── NOC_1_2_bouncingball_vectors │ └── NOC_1_2_bouncingball_vectors.rb ├── NOC_1_3_vector_subtraction │ └── NOC_1_3_vector_subtraction.rb ├── NOC_1_4_vector_multiplication │ └── NOC_1_4_vector_multiplication.rb ├── NOC_1_5_vector_magnitude │ └── NOC_1_5_vector_magnitude.rb ├── NOC_1_6_vector_normalize │ └── NOC_1_6_vector_normalize.rb ├── NOC_1_7_motion101 │ └── NOC_1_7_motion101.rb ├── NOC_1_8_motion101_acceleration │ └── NOC_1_8_motion101_acceleration.rb └── NOC_1_9_motion101_acceleration │ └── NOC_1_9_motion101_acceleration.rb ├── chp02_forces ├── Exercise_2_10_attractrepel │ ├── Exercise_2_10_attractrepel.rb │ ├── attractor.rb │ └── mover.rb ├── Extra_instantforce │ └── Extra_instantforce.rb ├── NOC_02forces_many_attraction_3D │ └── NOC_02forces_many_attraction_3D.rb ├── NOC_02forces_many_mutual_boundaries │ ├── NOC_02forces_many_mutual_boundaries.rb │ └── mover.rb ├── NOC_2_1_forces │ ├── NOC_2_1_forces.rb │ └── mover.rb ├── NOC_2_2_forces_many │ ├── NOC_2_2_forces_many.rb │ └── mover.rb ├── NOC_2_3_forces_many_realgravity │ ├── NOC_2_3_forces_many_realgravity.rb │ └── mover.rb ├── NOC_2_4_forces_friction │ ├── NOC_2_4_forces_friction.rb │ └── mover.rb ├── NOC_2_4_forces_nofriction │ ├── NOC_2_4_forces_nofriction.rb │ └── mover.rb ├── NOC_2_5_fluidresistance │ └── NOC_2_5_fluidresistance.rb ├── NOC_2_5_fluidresistance_sequence │ └── NOC_2_5_fluidresistance_sequence.rb ├── NOC_2_6_attraction │ ├── NOC_2_6_attraction.rb │ ├── attractor.rb │ └── mover.rb ├── NOC_2_7_attraction_many │ ├── NOC_2_7_attraction_many.rb │ ├── attractor.rb │ └── mover.rb └── NOC_2_8_mutual_attraction │ ├── NOC_2_8_mutual_attraction.rb │ └── mover.rb ├── chp03_oscillation ├── AdditiveWave │ └── AdditiveWave.rb ├── AttractionArrayWithOscillation │ ├── AttractionArrayWithOscillation.rb │ ├── attractor.rb │ ├── crawler.rb │ └── oscillator.rb ├── Exercise_3_01_exercise_baton │ └── Exercise_3_01_exercise_baton.rb ├── Exercise_3_03_cannon │ └── Exercise_3_03_cannon.rb ├── Exercise_3_04_spiral │ └── Exercise_3_04_spiral.rb ├── Exercise_3_05_asteroids │ └── Exercise_3_05_asteroids.rb ├── Exercise_3_10_OOPWave │ └── Exercise_3_10_OOPWave.rb ├── Exercise_3_11_AdditiveWave │ └── Exercise_3_11_AdditiveWave.rb ├── Exercise_3_16_springs │ └── Exercise_3_16_springs.rb ├── Exercise_3_16_springs_array │ └── Exercise_3_16_springs_array.rb ├── ExtraOscillatingBody │ └── ExtraOscillatingBody.rb ├── ExtraOscillatingUpAndDown │ └── ExtraOscillatingUpAndDown.rb ├── MultipleOscillations │ └── MultipleOscillations.rb ├── NOC_03spring_exercise_sine │ └── NOC_03spring_exercise_sine.rb ├── NOC_3_01_angular_motion │ └── NOC_3_01_angular_motion.rb ├── NOC_3_02_forces_angular_motion │ ├── NOC_3_02_forces_angular_motion.rb │ ├── attractor.rb │ └── mover.rb ├── NOC_3_03_pointing_velocity │ ├── NOC_3_03_pointing_velocity.rb │ └── mover.rb ├── NOC_3_04_PolarToCartesian_trail │ └── NOC_3_04_PolarToCartesian_trail.rb ├── NOC_3_05_simple_harmonic_motion │ └── NOC_3_05_simple_harmonic_motion.rb ├── NOC_3_06_simple_harmonic_motion │ └── NOC_3_06_simple_harmonic_motion.rb ├── NOC_3_07_oscillating_objects │ ├── NOC_3_07_oscillating_objects.rb │ └── oscillator.rb ├── NOC_3_08_static_wave_lines │ └── NOC_3_08_static_wave_lines.rb ├── NOC_3_09_exercise_additive_wave │ └── NOC_3_09_exercise_additive_wave.rb ├── NOC_3_09_wave │ └── NOC_3_09_wave.rb ├── NOC_3_10_PendulumExample │ └── NOC_3_10_PendulumExample.rb ├── NOC_3_10_PendulumExampleSimplified │ └── NOC_3_10_PendulumExampleSimplified.rb ├── NOC_3_11_spring │ └── NOC_3_11_spring.rb └── OOPWaveParticles │ └── OOPWaveParticles.rb ├── chp04_systems ├── NOC_4_01_SingleParticle │ └── NOC_4_01_SingleParticle.rb ├── NOC_4_01_SingleParticle_trail │ ├── NOC_4_01_SingleParticle_trail.rb │ └── particle.rb ├── NOC_4_02_ArrayListParticles │ ├── NOC_4_02_ArrayListParticles.rb │ └── particle.rb ├── NOC_4_03_ParticleSystemClass │ ├── NOC_4_03_ParticleSystemClass.rb │ ├── particle.rb │ └── particle_system.rb ├── NOC_4_05_ParticleSystemInheritancePolymorphism │ ├── NOC_4_05_ParticleSystemInheritancePolymorphism.rb │ ├── particle.rb │ └── particle_system.rb ├── NOC_4_06_ParticleSystemForces │ ├── NOC_4_06_ParticleSystemForces.rb │ ├── particle.rb │ └── particle_system.rb ├── NOC_4_07_ParticleSystemForcesRepeller │ ├── NOC_4_07_ParticleSystemForcesRepeller.rb │ ├── particle.rb │ ├── particle_system.rb │ └── repeller.rb ├── NOC_4_08_ParticleSystemSmoke_b │ ├── NOC_4_08_ParticleSystemSmoke_b.rb │ ├── data │ │ └── texture.png │ ├── particle.rb │ └── particle_system.rb └── NOC_4_09_AdditiveBlending │ ├── NOC_4_09_AdditiveBlending.rb │ ├── data │ └── texture.png │ ├── particle.rb │ └── particle_system.rb ├── chp05_physicslibraries ├── CollisionsEqualMass │ ├── collisions_equal_mass.rb │ ├── mover.rb │ └── world.rb ├── box2d │ ├── README.md │ ├── bumpy_surface_noise.rb │ ├── collision_listening.rb │ ├── data │ │ └── java_args.txt │ ├── distance_joint │ │ ├── boundary.rb │ │ ├── distance_joint.rb │ │ ├── pair.rb │ │ ├── particle.rb │ │ └── particle_system.rb │ ├── lib │ │ ├── boundary.rb │ │ ├── box.rb │ │ ├── custom_listener.rb │ │ ├── custom_shape.rb │ │ ├── particle.rb │ │ ├── particle_system.rb │ │ └── surface.rb │ ├── liquid_fun_test.rb │ ├── liquidy.rb │ ├── mouse_joint │ │ ├── boundary.rb │ │ ├── box.rb │ │ ├── dummy_spring.rb │ │ ├── mouse_joint.rb │ │ └── spring.rb │ ├── polygons.rb │ └── revolute_joint │ │ ├── box.rb │ │ ├── particle.rb │ │ ├── revolute_joint.rb │ │ └── windmill.rb └── toxiclibs │ ├── README.md │ ├── attract_repel │ ├── attract_repel.rb │ ├── attractor.rb │ └── particle.rb │ ├── force_directed_graph │ ├── cluster.rb │ ├── force_directed_graph.rb │ └── node.rb │ ├── simple_cluster │ ├── cluster.rb │ ├── node.rb │ └── simple_cluster.rb │ ├── simple_spring │ ├── particle.rb │ └── simple_spring.rb │ ├── soft_body │ ├── blanket.rb │ ├── connection.rb │ ├── particle.rb │ └── soft_body_square_adapted.rb │ └── soft_string │ ├── chain.rb │ ├── particle.rb │ └── soft_string_pendulum.rb ├── chp06_agents ├── NOC_6_01_Seek │ └── NOC_6_01_Seek.rb ├── NOC_6_01_Seek_trail │ └── NOC_6_01_Seek_trail.rb ├── NOC_6_02_Arrive │ └── NOC_6_02_Arrive.rb ├── NOC_6_03_StayWithinWalls │ └── NOC_6_03_StayWithinWalls.rb ├── NOC_6_03_StayWithinWalls_trail │ └── NOC_6_03_StayWithinWalls_trail.rb ├── NOC_6_04_Flowfield │ └── NOC_6_04_Flowfield.rb ├── NOC_6_05_PathFollowingSimple │ └── NOC_6_05_PathFollowingSimple.rb ├── NOC_6_06_PathFollowing │ └── NOC_6_06_PathFollowing.rb ├── NOC_6_07_Separation │ └── NOC_6_07_Separation.rb ├── NOC_6_08_SeparationAndSeek │ ├── NOC_6_08_SeparationAndSeek.rb │ └── vehicle.rb └── NOC_6_09_Flocking │ └── NOC_6_09_Flocking.rb ├── chp07_CA ├── NOC_7_01_WolframCA_figures │ └── NOC_7_01_WolframCA_figures.rb ├── NOC_7_01_WolframCA_simple │ └── NOC_7_01_WolframCA_simple.rb ├── NOC_7_02_GameOfLifeSimple │ └── NOC_7_02_GameOfLifeSimple.rb └── NOC_7_03_GameOfLifeOOP │ └── NOC_7_03_GameOfLifeOOP.rb ├── chp08_fractals ├── NOC_8_01_Recursion │ └── NOC_8_01_Recursion.rb ├── NOC_8_02_Recursion │ └── NOC_8_02_Recursion.rb ├── NOC_8_03_Recursion │ └── NOC_8_03_Recursion.rb ├── NOC_8_04_CantorSet │ └── NOC_8_04_CantorSet.rb ├── NOC_8_04_Tree │ └── NOC_8_04_Tree.rb ├── NOC_8_05_Koch │ └── NOC_8_05_Koch.rb ├── NOC_8_06_Tree │ └── NOC_8_06_Tree.rb ├── NOC_8_06_Tree_static │ └── NOC_8_06_Tree_static.rb ├── NOC_8_07_TreeStochastic │ └── NOC_8_07_TreeStochastic.rb └── NOC_8_09_LSystem │ └── NOC_8_09_LSystem.rb ├── chp09_ga ├── NOC_9_01_GA_Shakespeare │ └── NOC_9_01_GA_Shakespeare.rb ├── NOC_9_02_SmartRockets_superbasic │ ├── README.md │ ├── dna.rb │ ├── population.rb │ ├── rocket.rb │ └── smart_rocket_basic.rb ├── NOC_9_03_SmartRockets │ ├── NOC_9_03_SmartRockets.rb │ ├── dna.rb │ ├── obstacle.rb │ ├── population.rb │ └── rocket.rb ├── NOC_9_04_Faces_interactiveselection │ ├── face.rb │ ├── faces_interactive.rb │ └── population.rb └── NOC_9_05_EvolutionEcosystem │ ├── evolution_ecosystem.rb │ └── world.rb └── chp10_nn ├── LayeredNetwork ├── Exercise_10_05_LayeredNetworkAnimation.rb ├── LayeredNetworkViz.rb └── library │ └── neural_network │ ├── lib │ ├── connection.rb │ ├── network.rb │ ├── neuron.rb │ └── static_network.rb │ └── neural_network.rb ├── NOC_10_01_SimplePerceptron └── NOC_10_01_SimplePerceptron.rb ├── NOC_10_02_SeekingNeural └── NOC_10_02_SeekingNeural.rb ├── SimpleNetwork ├── NOC_10_03_NetworkViz.rb ├── NOC_10_04_NetworkAnimation.rb └── library │ └── neural_network │ ├── lib │ ├── connection.rb │ ├── network.rb │ └── neuron.rb │ └── neural_network.rb ├── xor ├── .gitignore ├── .mvn │ └── extensions.xml ├── README.md ├── Rakefile ├── landscape.rb ├── pom.rb ├── pom.xml ├── src │ └── main │ │ └── java │ │ └── nn │ │ ├── Connection.java │ │ ├── HiddenNeuron.java │ │ ├── InputNeuron.java │ │ ├── Network.java │ │ ├── Neuron.java │ │ └── OutputNeuron.java └── xor.rb └── xor_ruby ├── README.md ├── landscape.rb ├── library └── xor │ ├── lib │ ├── connection.rb │ ├── input_neuron.rb │ ├── network.rb │ └── neuron.rb │ ├── test │ ├── connection_test.rb │ ├── input_neuron_test.rb │ ├── network_test.rb │ ├── neuron_test.rb │ └── test_helper.rb │ └── xor.rb └── xor.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.gem 3 | *.rbc 4 | /.config 5 | /coverage/ 6 | /InstalledFiles 7 | /pkg/ 8 | /spec/reports/ 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalisation: 25 | /.bundle/ 26 | /vendor/bundle 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | # Gemfile.lock 32 | # .ruby-version 33 | # .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ruby-Processing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The-Nature-of-Code-Examples-in-JRubyArt # 2 | 3 | A new ruby port of the [examples][] from [The Nature of Code][] by Daniel Shiffman. Samples have been adapted to run with JRubyArt (processing-4.0+ and ruby-2.5+), from the [original ruby port][]. Some of the changes in this version are required because of changes to processing (eg. size now belongs in settings), but others have been made to take advantage of newer ruby syntax. In particular the fact keyword arguments are now first class citizens, so argument order should no longer be an issue, and method signatures can have more meaning. 4 | 5 | 6 | Although many of the sketches here stand on their own merit, you should remember that they were originally designed to illustrate the book (so to get the most from them you should definetly [get the book][]). Since the sketches have been translated to a more idiomatic ruby, some of the sketches may not exactly match the points made in the book. However such sketches do serve to demonstrate the differences between processing and JRubyArt. Since the book is now also available in [Japanese][] it would be great if someone would fork this and add Japanese annotations. For the most part the code is just ruby and processing, but see [glossary][] for some convenience methods unique to JRubyArt (eg `load_library`, `map1d`, `constrained_map`). 7 | 8 | ## Tested versions 9 | 10 | ruby 2.5.7 11 | 12 | jruby-9.2.14.0 13 | 14 | JRubyArt gem version 2.4.2 (processing-4.0) 15 | 16 | pbox2d gem version 1.0.3 17 | 18 | 19 | [The Nature of Code]:http://natureofcode.com 20 | [get the book]:http://natureofcode.com 21 | [Japanese]:http://www.amazon.co.jp/Nature-Code--Processing%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E8%87%AA%E7%84%B6%E7%8F%BE%E8%B1%A1%E3%81%AE%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3--%E3%83%80%E3%83%8B%E3%82%A8%E3%83%AB%E3%83%BB%E3%82%B7%E3%83%95%E3%83%9E%E3%83%B3/dp/4862462456/ 22 | [examples]:https://github.com/shiffman/The-Nature-of-Code-Examples 23 | [original ruby port]:https://github.com/ruby-processing/The-Nature-of-Code-Examples-in-Ruby 24 | [glossary]:https://github.com/ruby-processing/The-Nature-of-Code-for-JRubyArt/wiki/glossary 25 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_10_motion101_acceleration/NOC_1_10_motion101_acceleration.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | class Mover 4 | RADIUS = 24 5 | def initialize(location:) 6 | @location = location 7 | @velocity = Vec2D.new(0, 0) 8 | @topspeed = 6 9 | end 10 | 11 | def update 12 | mouse = Vec2D.new(mouse_x, mouse_y) 13 | acceleration = mouse - @location 14 | acceleration.normalize! 15 | acceleration *= 0.2 16 | @velocity += acceleration 17 | @velocity.set_mag(@topspeed) { @velocity.mag > @topspeed } 18 | @location += @velocity 19 | end 20 | 21 | def display 22 | stroke(0) 23 | stroke_weight(2) 24 | fill(127) 25 | ellipse(@location.x, @location.y, 2 * RADIUS, 2 * RADIUS) 26 | end 27 | end 28 | 29 | # NOC_1_10_motion101_acceleration 30 | def setup 31 | sketch_title 'Motion 101 Acceleration' 32 | @mover = Mover.new(location: Vec2D.new(rand(width / 2), rand(height / 2.0))) 33 | end 34 | 35 | def draw 36 | background(255) 37 | @mover.update 38 | @mover.display 39 | end 40 | 41 | def settings 42 | size(800, 200) 43 | end 44 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_11_motion101_acceleration_array/NOC_1_11_motion101_acceleration_array.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | # NOC_1_11_motion101_acceleration_array 4 | class Mover 5 | RADIUS = 24 6 | TOP_SPEED = 6 7 | attr_reader :location, :velocity, :topspeed_squared 8 | 9 | def initialize(max_x:, max_y:) 10 | @location = Vec2D.new( 11 | rand(RADIUS..max_x - RADIUS), 12 | rand(RADIUS..max_y - RADIUS) 13 | ) 14 | @velocity = Vec2D.new(0, 0) 15 | end 16 | 17 | def update_display 18 | update 19 | display 20 | end 21 | 22 | private 23 | 24 | def update 25 | mouse = Vec2D.new(mouse_x, mouse_y) 26 | acceleration = mouse - location 27 | acceleration.normalize! 28 | acceleration *= 0.2 29 | @velocity += acceleration 30 | velocity.set_mag(TOP_SPEED) { velocity.mag > TOP_SPEED } 31 | @location += velocity 32 | end 33 | 34 | def display 35 | stroke(0) 36 | stroke_weight(2) 37 | fill(127) 38 | ellipse(location.x, location.y, RADIUS * 2, RADIUS * 2) 39 | end 40 | end 41 | 42 | attr_reader :movers 43 | 44 | def setup 45 | sketch_title 'Motion 101 Acceleration Array' 46 | @movers = Array.new(20) { Mover.new(max_x: width, max_y: height) } 47 | end 48 | 49 | def draw 50 | background(255) 51 | movers.each(&:update_display) 52 | end 53 | 54 | def settings 55 | size(800, 200) 56 | end 57 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_1_bouncingball_novectors/NOC_1_1_bouncingball_novectors.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_1_bouncingball_novectors 2 | # http://natureofcode.com 3 | # Example 1-1: Bouncing Ball, no vectors 4 | RADIUS = 24 5 | 6 | def setup 7 | sketch_title 'Bouncing Ball No Vectors' 8 | @x = 100 9 | @y = 100 10 | @xspeed = 2.5 11 | @yspeed = 2 12 | end 13 | 14 | def draw 15 | background(255) 16 | # Add the current speed to the location. 17 | @x += @xspeed 18 | @y += @yspeed 19 | @xspeed = -@xspeed unless (RADIUS..width - RADIUS).cover?(@x) 20 | @yspeed = -@yspeed unless (RADIUS..height - RADIUS).cover?(@y) 21 | # Display circle at x location 22 | stroke(0) 23 | stroke_weight(2) 24 | fill(127) 25 | ellipse(@x, @y, 2 * RADIUS, 2 * RADIUS) 26 | end 27 | 28 | def settings 29 | size(800, 200) 30 | smooth 4 31 | end 32 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_2_bouncingball_vectors/NOC_1_2_bouncingball_vectors.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_2_bouncingball_vectors 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # Example 1-2: Bouncing Ball, with Vec2D! 5 | RADIUS = 8 6 | 7 | def setup 8 | sketch_title 'Bouncing Ball With Vectors' 9 | background(255) 10 | @loc = Vec2D.new(100.0, 100.0) 11 | @vel = Vec2D.new(2.5, 5.0) 12 | end 13 | 14 | def draw 15 | no_stroke 16 | fill(255, 10) 17 | rect(0, 0, width, height) 18 | # Add the current speed to the loc. 19 | @loc += @vel 20 | @vel.x = -@vel.x unless (RADIUS..width - RADIUS).cover? @loc.x 21 | @vel.y = -@vel.y unless (RADIUS..height - RADIUS).cover? @loc.y 22 | # Display circle at x loc 23 | stroke(0) 24 | fill(175) 25 | ellipse(@loc.x, @loc.y, RADIUS * 2, RADIUS * 2) 26 | end 27 | 28 | def settings 29 | size(200, 200) 30 | end 31 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_3_vector_subtraction/NOC_1_3_vector_subtraction.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_3_vector_subtraction.pde 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # Example 1-3: Vector subtraction 5 | # In JRubyArt you do cool things with Vec2D 6 | # like subtract assign using operators (-, = methods really) 7 | def setup 8 | sketch_title 'Vector Subtraction' 9 | end 10 | 11 | def draw 12 | background(255) 13 | mouse = Vec2D.new(mouse_x, mouse_y) 14 | center = Vec2D.new(width / 2, height / 2) 15 | mouse -= center 16 | translate(width / 2, height / 2) 17 | stroke_weight(2) 18 | stroke(0) 19 | line(0, 0, mouse.x, mouse.y) 20 | end 21 | 22 | def settings 23 | size(800, 200) 24 | smooth 4 25 | end 26 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_4_vector_multiplication/NOC_1_4_vector_multiplication.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_4_vector_multiplication 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # Example 1-4: Vector multiplication 5 | # More cool stuff with Vectors in JRubyArt 6 | # you can multiply a Vec2D with a scalar value 7 | 8 | def setup 9 | sketch_title 'Vector Multiplication' 10 | end 11 | 12 | def draw 13 | background 255 14 | mouse = Vec2D.new(mouse_x, mouse_y) 15 | center = Vec2D.new(width / 2, height / 2) 16 | mouse -= center 17 | # Multiplying a vector! 18 | mouse *= 0.5 19 | translate(width / 2, height / 2) 20 | stroke_weight(2) 21 | stroke(0) 22 | line(0, 0, mouse.x, mouse.y) 23 | end 24 | 25 | def settings 26 | size 800, 200 27 | smooth 4 28 | end 29 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_5_vector_magnitude/NOC_1_5_vector_magnitude.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_5_vector_magnitude 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # Example 1-5: Vector magnitude 5 | 6 | def setup 7 | sketch_title 'Vector Magnitude' 8 | end 9 | 10 | def draw 11 | background(255) 12 | mouse = Vec2D.new(mouse_x, mouse_y) 13 | center = Vec2D.new(width / 2, height / 2) 14 | mouse -= center 15 | fill(0) 16 | no_stroke 17 | rect(0, 0, mouse.mag, 10) 18 | translate(width / 2, height / 2) 19 | stroke(0) 20 | stroke_weight(2) 21 | line(0, 0, mouse.x, mouse.y) 22 | end 23 | 24 | def settings 25 | size(800, 200) 26 | smooth 4 27 | end 28 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_6_vector_normalize/NOC_1_6_vector_normalize.rb: -------------------------------------------------------------------------------- 1 | # NOC_1_6_vector_normalize 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Demonstration of normalizing a vector. 6 | # Normalizing a vector sets its length to 1. 7 | 8 | def setup 9 | sketch_title 'Vector Normalize' 10 | end 11 | 12 | def draw 13 | background(255) 14 | # A vector that points to the mouse location 15 | mouse = Vec2D.new(mouse_x, mouse_y) 16 | # A vector that points to the center of the window 17 | center = Vec2D.new(width / 2, height / 2) 18 | # Subtract center from mouse which results in a vector that points from center 19 | # to mouse 20 | mouse -= center 21 | # Normalize the vector (bang version, changes original) 22 | mouse.normalize! 23 | # Multiply its length by 50 24 | mouse *= 50 25 | translate(width / 2, height / 2) 26 | # Draw the resulting vector 27 | stroke(0) 28 | stroke_weight(2) 29 | line(0, 0, mouse.x, mouse.y) 30 | end 31 | 32 | def settings 33 | size 800, 200 34 | smooth 4 35 | end 36 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_7_motion101/NOC_1_7_motion101.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | class Mover 4 | RADIUS = 24 5 | attr_reader :location 6 | 7 | def initialize(width, height) 8 | @location = Vec2D.new(rand(0..width), rand(0..height)) 9 | @velocity = Vec2D.new(rand(-2.0..2.0), rand(-2.0..2.0)) 10 | end 11 | 12 | def update 13 | @location += @velocity 14 | end 15 | 16 | def display 17 | stroke(0) 18 | stroke_weight(2) 19 | fill(127) 20 | ellipse(location.x, location.y, RADIUS * 2, RADIUS * 2) 21 | end 22 | 23 | def check_edges(width, height) 24 | unless (RADIUS..width - RADIUS).cover? location.x 25 | location.x = RADIUS if location.x > width - RADIUS 26 | location.x = width - RADIUS if location.x < RADIUS 27 | end 28 | return if (RADIUS..height - RADIUS).cover? location.y 29 | 30 | location.y = RADIUS if location.y > height - RADIUS 31 | location.y = height - RADIUS if location.y < RADIUS 32 | end 33 | end 34 | 35 | # NOC_1_7_motion101 36 | attr_reader :mover 37 | 38 | def setup 39 | sketch_title 'Motion 101' 40 | @mover = Mover.new(width, height) 41 | end 42 | 43 | def draw 44 | background(255) 45 | mover.update 46 | mover.check_edges(width, height) 47 | mover.display 48 | end 49 | 50 | def settings 51 | size(800, 200) 52 | end 53 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_8_motion101_acceleration/NOC_1_8_motion101_acceleration.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | class Mover 4 | RADIUS = 24 5 | attr_reader :max_x, :max_y, :location 6 | 7 | def initialize(max_x:, max_y:) 8 | @max_x = max_x - RADIUS 9 | @max_y = max_y - RADIUS 10 | @location = Vec2D.new(rand(RADIUS..max_x), rand(RADIUS..max_y)) 11 | @velocity = Vec2D.new(0, 0) 12 | @acceleration = Vec2D.new(-0.001, 0.01) 13 | @topspeed = 10 14 | end 15 | 16 | def update 17 | @velocity += @acceleration 18 | @velocity.set_mag(@topspeed) { @velocity.mag > @topspeed } 19 | @location += @velocity 20 | end 21 | 22 | def display 23 | stroke(0) 24 | stroke_weight(2) 25 | fill(127) 26 | ellipse(@location.x, @location.y, RADIUS * 2, RADIUS * 2) 27 | end 28 | 29 | def check_edges 30 | unless (RADIUS..max_x).cover? location.x 31 | location.x = RADIUS if location.x > max_x 32 | location.x = max_x if location.x < RADIUS 33 | end 34 | return if (RADIUS..max_y).cover? location.y 35 | 36 | location.y = RADIUS if location.y > max_y 37 | location.y = max_y if location.y < RADIUS 38 | end 39 | end 40 | 41 | # NOC_1_8_motion101_acceleration 42 | def setup 43 | sketch_title 'Motion 101 Acceleration' 44 | @mover = Mover.new(max_x: width, max_y: height) 45 | end 46 | 47 | def draw 48 | background(255) 49 | @mover.update 50 | @mover.check_edges 51 | @mover.display 52 | end 53 | 54 | def settings 55 | size(800, 200) 56 | end 57 | -------------------------------------------------------------------------------- /chp01_vectors/NOC_1_9_motion101_acceleration/NOC_1_9_motion101_acceleration.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | class Mover 4 | RADIUS = 24 5 | attr_reader :max_y, :max_x, :location 6 | 7 | def initialize(max_x:, max_y:) 8 | @max_x = max_x - RADIUS 9 | @max_y = max_y - RADIUS 10 | @location = Vec2D.new(rand(RADIUS..max_x), rand(RADIUS..max_y)) 11 | @velocity = Vec2D.new(0, 0) 12 | @topspeed = 6 13 | end 14 | 15 | def update 16 | acceleration = Vec2D.random 17 | acceleration *= rand(0..2.0) 18 | @velocity += acceleration 19 | @velocity.set_mag(@topspeed) { @velocity.mag > @topspeed } 20 | @location += @velocity 21 | end 22 | 23 | def display 24 | stroke(0) 25 | stroke_weight(2) 26 | fill(127) 27 | ellipse(@location.x, @location.y, 2 * RADIUS, 2 * RADIUS) 28 | end 29 | 30 | def check_edges 31 | unless (RADIUS..max_x).cover? location.x 32 | location.x = RADIUS if location.x > max_x 33 | location.x = max_x if location.x < RADIUS 34 | end 35 | return if (RADIUS..max_y).cover? location.y 36 | 37 | location.y = RADIUS if location.y > max_y 38 | location.y = max_y if location.y < RADIUS 39 | end 40 | end 41 | 42 | # NOC_1_9_motion101_acceleration 43 | def setup 44 | sketch_title 'Motion 101 Acceleration' 45 | @mover = Mover.new(max_x: width, max_y: height) 46 | end 47 | 48 | def draw 49 | background(255) 50 | @mover.update 51 | @mover.check_edges 52 | @mover.display 53 | end 54 | 55 | def settings 56 | size(800, 200) 57 | end 58 | -------------------------------------------------------------------------------- /chp02_forces/Exercise_2_10_attractrepel/Exercise_2_10_attractrepel.rb: -------------------------------------------------------------------------------- 1 | # Exercise_2_10_attractrepel 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'attractor' 5 | require_relative 'mover' 6 | 7 | def setup 8 | sketch_title 'Attract Repel Exercise' 9 | @attractor = Attractor.new(location: Vec2D.new(width / 2, height / 2)) 10 | @movers = (0..19).map do 11 | Mover.new( 12 | mass: rand(4.0..12), 13 | location: Vec2D.new(rand(width), rand(height)) 14 | ) 15 | end 16 | end 17 | 18 | def draw 19 | background(255) 20 | @attractor.display 21 | @movers.each do |m| 22 | @movers.each do |mm| 23 | next if m.equal? mm 24 | 25 | repel = mm.repel(mover: m) 26 | m.apply_force(force: repel) 27 | end 28 | attraction = @attractor.attract(mover: m) 29 | m.apply_force(force: attraction) 30 | m.update 31 | m.display 32 | end 33 | end 34 | 35 | def settings 36 | size(800, 200) 37 | end 38 | -------------------------------------------------------------------------------- /chp02_forces/Exercise_2_10_attractrepel/attractor.rb: -------------------------------------------------------------------------------- 1 | # Attractor class 2 | class Attractor 3 | include Processing::Proxy 4 | 5 | attr_reader :mass, :location 6 | 7 | def initialize(location:) 8 | @location = location 9 | @mass = 10 10 | @drag_offset = Vec2D.new(0.0, 0.0) 11 | @dragging = false 12 | @rollover = false 13 | end 14 | 15 | def attract(mover:) 16 | force = location - mover.location # Calculate direction of force 17 | d = force.mag # Distance between objects 18 | # Limit the distance to eliminate "extremes" 19 | d = constrain(d, 5.0, 25.0) 20 | force.normalize! # Normalize vector to get direction 21 | strength = (G * mass * mover.mass) / (d * d) # magnitude of g. force 22 | force *= strength # Get force vector --> magnitude * direction 23 | force 24 | end 25 | 26 | def display 27 | ellipse_mode CENTER 28 | stroke 0 29 | if @dragging 30 | fill 50 31 | elsif @rollover 32 | fill 100 33 | else 34 | fill 0 35 | end 36 | ellipse(location.x, location.y, mass * 6, mass * 6) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /chp02_forces/Exercise_2_10_attractrepel/mover.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | G = 1 # kind of pointless gravitational constant 4 | 5 | # Mover class 6 | class Mover 7 | include Processing::Proxy 8 | attr_reader :location, :mass 9 | 10 | def initialize(location:, mass:) 11 | @location = location 12 | @velocity = Vec2D.new 13 | @acceleration = Vec2D.new 14 | @mass = mass 15 | end 16 | 17 | def apply_force(force:) 18 | @acceleration += force / @mass 19 | end 20 | 21 | def update 22 | @velocity += @acceleration 23 | @location += @velocity 24 | @acceleration *= 0 25 | end 26 | 27 | def display 28 | stroke(0) 29 | fill(175, 200) 30 | ellipse(@location.x, @location.y, @mass * 2, @mass * 2) 31 | end 32 | 33 | def repel(mover:) 34 | force = @location - mover.location # Calculate direction of force 35 | distance = force.mag # Distance between objects 36 | # Limit "extreme" results for very close or very far objects 37 | distance = constrain(distance, 1.0, 10_000.0) 38 | # Normalize we just want this vector for direction 39 | force.normalize! 40 | # Calculate gravitional force magnitude 41 | strength = (G * @mass * mover.mass) / (distance * distance) 42 | force *= -strength # Get force vector --> magnitude * direction 43 | force 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /chp02_forces/Extra_instantforce/Extra_instantforce.rb: -------------------------------------------------------------------------------- 1 | # Extra_instantforce 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | class Mover 5 | attr_reader :location 6 | 7 | def initialize(location:) 8 | @location = location 9 | @velocity = Vec2D.new(0, 0) 10 | @acceleration = Vec2D.new(0, 0) 11 | @mass = 1 12 | end 13 | 14 | def shake 15 | force = Vec2D.random 16 | force *= 0.7 17 | apply_force(force) 18 | end 19 | 20 | def apply_force(force:) 21 | @acceleration += force / @mass 22 | end 23 | 24 | def update 25 | @velocity += @acceleration 26 | @location += @velocity 27 | @acceleration *= 0 28 | # Simple friction 29 | @velocity *= 0.95 30 | end 31 | 32 | def display 33 | stroke(0) 34 | stroke_weight(2) 35 | fill(127) 36 | ellipse(@location.x, @location.y, 48, 48) 37 | end 38 | end 39 | 40 | # Extra_instantforce 41 | # The Nature of Code 42 | # http://natureofcode.com 43 | 44 | def setup 45 | sketch_title 'Extra Instant Force' 46 | @mover = Mover.new(location: Vec2D.new(width, height) / 2.0) 47 | @t = 0.0 48 | end 49 | 50 | def draw 51 | background(255) 52 | # Perlin noise wind 53 | wx = map1d(noise(@t), (-1.0..1.0), (-1..1.0)) 54 | wind = Vec2D.new(wx, 0) 55 | @t += 0.01 56 | line(width / 2, height / 2, width / 2 + wind.x * 100, height / 2 + wind.y * 100) 57 | @mover.apply_force(force: wind) 58 | if @mover.location.x > width - 50 59 | boundary = Vec2D.new(-1, 0) 60 | @mover.apply_force(force: boundary) 61 | elsif @mover.location.x < 50 62 | boundary = Vec2D.new(1, 0) 63 | @mover.apply_force(force: boundary) 64 | end 65 | @mover.update 66 | @mover.display 67 | end 68 | 69 | # Instant Force 70 | def mouse_pressed 71 | cannon = Vec2D.random 72 | cannon *= 5 73 | @mover.apply_force(force: cannon) 74 | end 75 | 76 | def settings 77 | size(640, 360) 78 | end 79 | -------------------------------------------------------------------------------- /chp02_forces/NOC_02forces_many_mutual_boundaries/NOC_02forces_many_mutual_boundaries.rb: -------------------------------------------------------------------------------- 1 | # NOC_02forces_many_mutual_boundaries 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | require_relative 'mover' 6 | attr_reader :movers 7 | 8 | SIZE = 20 9 | 10 | def setup 11 | sketch_title 'Forces Many Mutual Boundaries' 12 | @movers = (0..SIZE).map { Mover.new(rand(1.0..2), rand(width), rand(height)) } 13 | end 14 | 15 | def draw 16 | background(255) 17 | SIZE.times do |i| 18 | SIZE.times do |j| 19 | unless i == j 20 | attractor = movers[j].attract(mover: movers[i]) 21 | movers[i].apply_force(force: attractor) 22 | end 23 | end 24 | movers[i].boundaries(max_x: width, max_y: height) 25 | movers[i].run 26 | end 27 | end 28 | 29 | def settings 30 | size(640, 360) 31 | end 32 | -------------------------------------------------------------------------------- /chp02_forces/NOC_02forces_many_mutual_boundaries/mover.rb: -------------------------------------------------------------------------------- 1 | # The mover class 2 | class Mover 3 | include Processing::Proxy 4 | G = 0.4 # gravitational constant 5 | 6 | attr_reader :acceleration, :location, :mass, :velocity 7 | 8 | def initialize(m, x, y) 9 | @mass = m 10 | @location = Vec2D.new(x, y) 11 | @velocity = Vec2D.new 12 | @acceleration = Vec2D.new 13 | end 14 | 15 | def apply_force(force:) 16 | @acceleration += force / mass 17 | end 18 | 19 | def update 20 | @velocity += acceleration 21 | @location += velocity 22 | @acceleration *= 0 23 | end 24 | 25 | def display 26 | stroke(0) 27 | fill(175, 200) 28 | ellipse(location.x, location.y, mass * 16, mass * 16) 29 | end 30 | 31 | def run 32 | update 33 | display 34 | end 35 | 36 | def attract(mover:) 37 | force = location - mover.location # Calculate direction of force 38 | distance = force.mag # Distance between objects 39 | distance = constrain(distance, 5.0, 25.0) # Limiting the distance to eliminate "extreme" results for very close or very far objects 40 | force.normalize! # Normalize vector (distance doesn't matter here, we just want this vector for direction 41 | strength = (G * mass * mover.mass) / (distance * distance) # Calculate gravitional force magnitude 42 | force *= strength # Get force vector --> magnitude * direction 43 | end 44 | 45 | def boundaries(max_x:, max_y:) 46 | d = 50 47 | boundary = Vec2D.new 48 | if location.x < d 49 | boundary.x = 1 50 | elsif location.x > max_x - d 51 | boundary.x = -1 52 | end 53 | if location.y < d 54 | boundary.y = 1 55 | elsif location.y > max_y - d 56 | boundary.y = -1 57 | end 58 | boundary.set_mag 0.1 59 | apply_force(force: boundary) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_1_forces/NOC_2_1_forces.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | 5 | attr_reader :m 6 | 7 | def setup 8 | sketch_title 'Forces' 9 | @m = Mover.new 10 | end 11 | 12 | def draw 13 | background(255) 14 | wind = Vec2D.new(0.01, 0) 15 | gravity = Vec2D.new(0, 0.1) 16 | m.apply_force(force: wind) 17 | m.apply_force(force: gravity) 18 | m.update 19 | m.display 20 | m.check_edges(max_x: width, max_y: height) 21 | end 22 | 23 | def settings 24 | size(640, 360) 25 | end 26 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_1_forces/mover.rb: -------------------------------------------------------------------------------- 1 | # the mover class 2 | class Mover 3 | include Processing::Proxy 4 | RADIUS = 24 5 | def initialize 6 | @location = Vec2D.new(30, 30) 7 | @velocity = Vec2D.new 8 | @acceleration = Vec2D.new 9 | @mass = 1 10 | end 11 | 12 | def apply_force(force:) 13 | @acceleration += force / @mass 14 | end 15 | 16 | def update 17 | @velocity += @acceleration 18 | @location += @velocity 19 | @acceleration *= 0 20 | end 21 | 22 | def display 23 | stroke(0) 24 | stroke_weight(2) 25 | fill(127) 26 | ellipse(@location.x, @location.y, 2 * RADIUS, 2 * RADIUS) 27 | end 28 | 29 | def check_edges(max_x:, max_y:) 30 | xmax = max_x - RADIUS 31 | ymax = max_y - RADIUS 32 | if @location.x > xmax 33 | @location.x = xmax 34 | @velocity.x *= -1 35 | elsif @location.x < RADIUS 36 | @location.x = RADIUS 37 | @velocity.x *= -1 38 | end 39 | if @location.y > ymax 40 | @location.y = ymax 41 | @velocity.y *= -1 42 | elsif @location.y < RADIUS 43 | @location.y = RADIUS 44 | @velocity.y *= -1 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_2_forces_many/NOC_2_2_forces_many.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | 5 | def setup 6 | sketch_title 'Forces Many' 7 | @movers = Array.new(20) { Mover.new(rand(0.1..4), 0, 0) } 8 | end 9 | 10 | def draw 11 | background(255) 12 | @movers.each do |m| 13 | wind = Vec2D.new(0.01, 0) 14 | gravity = Vec2D.new(0, 0.1) 15 | m.apply_force(force: wind) 16 | m.apply_force(force: gravity) 17 | m.update 18 | m.display 19 | m.check_edges(max_x: width, max_y: height) 20 | end 21 | end 22 | 23 | def settings 24 | size(800, 200) 25 | end 26 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_2_forces_many/mover.rb: -------------------------------------------------------------------------------- 1 | # mover class 2 | class Mover 3 | include Processing::Proxy 4 | 5 | def initialize(mass, x, y) 6 | @location = Vec2D.new(x, y) 7 | @velocity = Vec2D.new(0, 0) 8 | @acceleration = Vec2D.new(0, 0) 9 | @mass = mass 10 | end 11 | 12 | def apply_force(force:) 13 | @acceleration += force / @mass 14 | end 15 | 16 | def update 17 | @velocity += @acceleration 18 | @location += @velocity 19 | @acceleration *= 0 20 | end 21 | 22 | def display 23 | stroke(0) 24 | stroke_weight(2) 25 | fill(127) 26 | ellipse(@location.x, @location.y, @mass * 16, @mass * 16) 27 | end 28 | 29 | def check_edges(max_x:, max_y:) 30 | if @location.x > max_x 31 | @location.x = max_x 32 | @velocity.x *= -1 33 | elsif @location.x < 0 34 | @location.x = 0 35 | @velocity.x *= -1 36 | end 37 | 38 | if @location.y > max_y 39 | @location.y = max_y 40 | @velocity.y *= -1 41 | elsif @location.y < 0 42 | @location.y = 0 43 | @velocity.y *= -1 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_3_forces_many_realgravity/NOC_2_3_forces_many_realgravity.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | 5 | SIZE = 20 6 | 7 | def setup 8 | sketch_title 'Noc 2 3 Forces Many Realgravity' 9 | @movers = (0..SIZE).map { Mover.new(rand(1.0..4), 0, 0) } 10 | end 11 | 12 | def draw 13 | background(255) 14 | @movers.each do |m| 15 | wind = Vec2D.new(0.01, 0) 16 | gravity = Vec2D.new(0, 0.1 * m.mass) 17 | m.apply_force(force: wind) 18 | m.apply_force(force: gravity) 19 | m.update 20 | m.display 21 | m.check_edges(max_x: width, max_y: height) 22 | end 23 | end 24 | 25 | def settings 26 | size(800, 200) 27 | end 28 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_3_forces_many_realgravity/mover.rb: -------------------------------------------------------------------------------- 1 | # Mover class 2 | class Mover 3 | include Processing::Proxy 4 | 5 | attr_reader :mass 6 | 7 | def initialize(mass, x, y) 8 | @location = Vec2D.new(x, y) 9 | @velocity = Vec2D.new(0, 0) 10 | @acceleration = Vec2D.new(0, 0) 11 | @mass = mass 12 | end 13 | 14 | def apply_force(force:) 15 | @acceleration += force / @mass 16 | end 17 | 18 | def update 19 | @velocity += @acceleration 20 | @location += @velocity 21 | @acceleration *= 0 22 | end 23 | 24 | def display 25 | stroke(0) 26 | stroke_weight(2) 27 | fill(127) 28 | ellipse(@location.x, @location.y, @mass * 16, @mass * 16) 29 | end 30 | 31 | def check_edges(max_x:, max_y:) 32 | if @location.x > max_x 33 | @location.x = max_x 34 | @velocity.x *= -1 35 | elsif @location.x < 0 36 | @location.x = 0 37 | @velocity.x *= -1 38 | end 39 | if @location.y > max_y 40 | @location.y = max_y 41 | @velocity.y *= -1 42 | elsif @location.y < 0 43 | @location.y = 0 44 | @velocity.y *= -1 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_4_forces_friction/NOC_2_4_forces_friction.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | SIZE = 15 5 | 6 | def setup 7 | sketch_title 'Forces Friction' 8 | srand(1) 9 | @movers = (0..SIZE).map { Mover.new(rand(1.0..4), rand(width), 0) } 10 | end 11 | 12 | def draw 13 | background(255) 14 | @movers.each do |m| 15 | wind = Vec2D.new(0.01, 0) 16 | gravity = Vec2D.new(0, 0.1 * m.mass) 17 | c = 0.05 18 | friction = m.velocity.copy 19 | friction *= -1 20 | friction.set_mag c 21 | m.apply_forces(wind, gravity, friction) 22 | m.update 23 | m.display 24 | m.check_edges(max_x: width, max_y: height) 25 | end 26 | end 27 | 28 | def settings 29 | size(383, 200) 30 | end 31 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_4_forces_friction/mover.rb: -------------------------------------------------------------------------------- 1 | # Mover class 2 | class Mover 3 | include Processing::Proxy 4 | 5 | attr_reader :acceleration, :location, :mass, :radius, :velocity 6 | 7 | def initialize(mass, x, y) 8 | @location = Vec2D.new(x, y) 9 | @velocity = Vec2D.new 10 | @acceleration = Vec2D.new 11 | @mass = mass 12 | @radius = mass * 8 13 | end 14 | 15 | def apply_force(force) 16 | @acceleration += force / mass 17 | end 18 | 19 | def apply_forces(*forces) 20 | force = forces.reduce(:+) 21 | apply_force(force) 22 | end 23 | 24 | def update 25 | @velocity += acceleration 26 | @location += velocity 27 | @acceleration *= 0 28 | end 29 | 30 | def display 31 | stroke(0) 32 | stroke_weight(2) 33 | fill(0, 127) 34 | ellipse(location.x, location.y, radius * 2, radius * 2) 35 | end 36 | 37 | def check_edges(max_x:, max_y:) 38 | max_x -= radius 39 | max_y -= radius 40 | if location.x > max_x 41 | @location.x = max_x 42 | @velocity.x *= -1 43 | elsif location.x < radius 44 | @location.x = radius 45 | @velocity.x *= -1 46 | end 47 | if location.y > max_y 48 | @location.y = max_y 49 | @velocity.y *= -1 50 | elsif location.y < radius 51 | @location.y = radius 52 | @velocity.y *= -1 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_4_forces_nofriction/NOC_2_4_forces_nofriction.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | 5 | SIZE = 15 6 | 7 | def setup 8 | sketch_title 'Forces No Friction' 9 | srand(1) 10 | @movers = (0..SIZE).map { Mover.new(rand(1.0..4), rand(width), 0) } 11 | end 12 | 13 | def draw 14 | background(255) 15 | @movers.each do |m| 16 | wind = Vec2D.new(0.01, 0) 17 | gravity = Vec2D.new(0, 0.1 * m.mass) 18 | m.apply_forces(wind, gravity) 19 | m.update 20 | m.display 21 | m.check_edges(max_x: width, max_y: height) 22 | end 23 | end 24 | 25 | def settings 26 | size(383, 200) 27 | end 28 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_4_forces_nofriction/mover.rb: -------------------------------------------------------------------------------- 1 | class Mover 2 | include Processing::Proxy 3 | 4 | attr_reader :acceleration, :location, :mass, :radius, :velocity 5 | 6 | def initialize(mass, x, y) 7 | @location = Vec2D.new(x, y) 8 | @velocity = Vec2D.new 9 | @acceleration = Vec2D.new 10 | @mass = mass 11 | @radius = mass * 8 12 | end 13 | 14 | def apply_force(force) 15 | @acceleration += force / mass 16 | end 17 | 18 | def apply_forces(*forces) 19 | force = forces.reduce(:+) 20 | apply_force(force) 21 | end 22 | 23 | def update 24 | @velocity += acceleration 25 | @location += velocity 26 | @acceleration *= 0 27 | end 28 | 29 | def display 30 | stroke(0) 31 | stroke_weight(2) 32 | fill(0, 127) 33 | ellipse(location.x, location.y, radius * 2, radius * 2) 34 | end 35 | 36 | def check_edges(max_x:, max_y:) 37 | max_x -= radius 38 | max_y -= radius 39 | if location.x > max_x 40 | @location.x = max_x 41 | @velocity.x *= -1 42 | elsif location.x < radius 43 | @location.x = radius 44 | @velocity.x *= -1 45 | end 46 | if location.y > max_y 47 | @location.y = max_y 48 | @velocity.y *= -1 49 | elsif location.y < radius 50 | @location.y = radius 51 | @velocity.y *= -1 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_6_attraction/NOC_2_6_attraction.rb: -------------------------------------------------------------------------------- 1 | # NOC_2_6_attraction 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # A class for a draggable attractive body in our world 5 | 6 | require_relative 'attractor' 7 | require_relative 'mover' 8 | 9 | attr_reader :attractor, :attractor_mover 10 | 11 | def setup 12 | sketch_title 'Attraction' 13 | @attractor_mover = Mover.new 14 | @attractor = Attractor.new(location: Vec2D.new(width / 2, height / 2)) 15 | end 16 | 17 | def draw 18 | background(255) 19 | attraction = attractor.attract(mover: attractor_mover) 20 | attractor_mover.apply_force(force: attraction) 21 | attractor_mover.update 22 | attractor.drag(position: Vec2D.new(mouse_x, mouse_y)) 23 | attractor.hover(position: Vec2D.new(mouse_x, mouse_y)) 24 | attractor.display 25 | attractor_mover.display 26 | end 27 | 28 | def mouse_pressed 29 | attractor.clicked(position: Vec2D.new(mouse_x, mouse_y)) 30 | end 31 | 32 | def mouse_released 33 | attractor.stop_dragging 34 | end 35 | 36 | def settings 37 | size(640, 360) 38 | end 39 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_6_attraction/mover.rb: -------------------------------------------------------------------------------- 1 | # A simple mover class 2 | class Mover 3 | include Processing::Proxy 4 | 5 | attr_reader :acceleration, :mass, :velocity, :location, :diameter 6 | 7 | def initialize 8 | @location = Vec2D.new(400, 50) 9 | @velocity = Vec2D.new(1, 0) 10 | @acceleration = Vec2D.new(0, 0) 11 | @mass = 1 12 | @diameter = mass * 24 13 | end 14 | 15 | def apply_force(force:) 16 | @acceleration += force / mass 17 | end 18 | 19 | def update 20 | @velocity += acceleration 21 | @location += velocity 22 | @acceleration *= 0 23 | end 24 | 25 | def display 26 | stroke(0) 27 | stroke_weight(2) 28 | fill(127) 29 | ellipse(location.x, location.y, diameter, diameter) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_7_attraction_many/NOC_2_7_attraction_many.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOC_2_7_attraction_many 4 | require_relative 'mover' 5 | require_relative 'attractor' 6 | 7 | def setup 8 | sketch_title 'Attractor Many' 9 | @movers = (0..9).map do 10 | Mover.new( 11 | location: Vec2D.new(rand(width), rand(height)), 12 | mass: rand(0.1..2) 13 | ) 14 | end 15 | @attractor = Attractor.new(location: Vec2D.new(width / 2, height / 2)) 16 | end 17 | 18 | def draw 19 | background(255) 20 | @attractor.display 21 | @attractor.drag(position: Vec2D.new(mouse_x, mouse_y)) 22 | @attractor.hover(position: Vec2D.new(mouse_x, mouse_y)) 23 | @movers.each do |m| 24 | m.apply_force(force: @attractor.attract(mover: m)) 25 | m.update 26 | m.display 27 | end 28 | end 29 | 30 | def mouse_pressed 31 | @attractor.clicked(position: Vec2D.new(mouse_x, mouse_y)) 32 | end 33 | 34 | def mouse_released 35 | @attractor.stop_dragging 36 | end 37 | 38 | def settings 39 | size(640, 360) 40 | end 41 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_7_attraction_many/attractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOC_2_7_attraction_many 4 | # The Nature of Code 5 | # http://natureofcode.com 6 | # A class for a draggable attractive body in our world 7 | class Attractor 8 | include Processing::Proxy 9 | G = 1 10 | attr_reader :location, :mass 11 | 12 | def initialize(location:) 13 | @location = location 14 | @mass = 20 15 | @drag_offset = Vec2D.new 16 | @dragging = false 17 | @rollover = false 18 | end 19 | 20 | def attract(mover:) 21 | # Calculate direction of force 22 | force = location - mover.location 23 | # Distance between objects 24 | dist = force.mag 25 | # Limit the dististance to avoid "extreme" results 26 | dist = constrain(dist, 5.0, 25.0) 27 | # Normalize vector, we just want the vector direction 28 | force.normalize! 29 | # Calculate magnitude of gravitional force 30 | strength = (G * mass * mover.mass) / (dist * dist) 31 | force *= strength # Calculate force vector --> magnitude * direction 32 | force 33 | end 34 | 35 | def display 36 | ellipse_mode CENTER 37 | stroke_weight 4 38 | stroke 0 39 | if @dragging 40 | fill 50 41 | elsif @rollover 42 | fill 100 43 | else 44 | fill 175, 200 45 | end 46 | ellipse(@location.x, @location.y, mass * 2, mass * 2) 47 | end 48 | 49 | # The methods below are for mouse interaction 50 | def clicked(position:) 51 | dist = position.dist(location) 52 | return unless dist < @mass 53 | 54 | @dragging = true 55 | @drag_offset = location - position 56 | end 57 | 58 | def hover(position:) 59 | dist = position.dist(location) 60 | @rollover = dist < @mass 61 | end 62 | 63 | def stop_dragging 64 | @dragging = false 65 | end 66 | 67 | def drag(position:) 68 | return unless @dragging 69 | 70 | @location = position + @drag_offset 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_7_attraction_many/mover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOC_2_7_attraction_many 4 | # The Nature of Code 5 | # http://natureofcode.com 6 | # A class for a draggable attractive body in our world 7 | class Mover 8 | include Processing::Proxy 9 | 10 | attr_reader :mass, :velocity, :location, :diameter 11 | 12 | def initialize(location:, mass:) 13 | @location = location 14 | @velocity = Vec2D.new(1, 0) 15 | @acceleration = Vec2D.new 16 | @mass = mass 17 | @diameter = mass * 25 18 | end 19 | 20 | def apply_force(force:) 21 | @acceleration += force / @mass 22 | end 23 | 24 | def update 25 | @velocity += @acceleration 26 | @location += @velocity 27 | @acceleration *= 0 28 | end 29 | 30 | def display 31 | stroke(0) 32 | stroke_weight(2) 33 | fill(0, 100) 34 | ellipse(@location.x, @location.y, diameter, diameter) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_8_mutual_attraction/NOC_2_8_mutual_attraction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOC_2_8_mutual_attraction 4 | # http://natureofcode.com 5 | require_relative 'mover' 6 | 7 | def setup 8 | sketch_title 'Mutual Attraction' 9 | @movers = (0..19).map do 10 | Mover.new( 11 | location: Vec2D.new(rand(width), rand(height)), 12 | mass: rand(0.1..2) 13 | ) 14 | end 15 | end 16 | 17 | def draw 18 | background(255) 19 | @movers.each do |moover| 20 | @movers.each do |other| 21 | next if moover.equal? other 22 | 23 | moover.apply_force(force: other.attract(mover: moover)) 24 | end 25 | moover.run 26 | end 27 | end 28 | 29 | def settings 30 | size(800, 200) 31 | end 32 | -------------------------------------------------------------------------------- /chp02_forces/NOC_2_8_mutual_attraction/mover.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Mover class 4 | class Mover 5 | include Processing::Proxy 6 | 7 | attr_reader :acceleration, :mass, :velocity, :location 8 | 9 | G = 0.4 10 | 11 | def initialize(location:, mass:) 12 | @location = location 13 | @velocity = Vec2D.new(0, 0) 14 | @acceleration = Vec2D.new(0, 0) 15 | @mass = mass 16 | end 17 | 18 | def apply_force(force:) 19 | @acceleration += force / mass 20 | end 21 | 22 | def update 23 | @velocity += acceleration 24 | @location += velocity 25 | @acceleration *= 0 26 | end 27 | 28 | def display 29 | stroke(0) 30 | stroke_weight(2) 31 | fill(0, 100) 32 | ellipse(location.x, location.y, mass * 24, mass * 24) 33 | end 34 | 35 | def run 36 | update 37 | display 38 | end 39 | 40 | def attract(mover:) 41 | force = location - mover.location 42 | distance = force.mag 43 | distance = constrain(distance, 5.0, 25.0) 44 | force.normalize! 45 | strength = (G * mass * mass) / (distance * distance) 46 | force *= strength 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /chp03_oscillation/AdditiveWave/AdditiveWave.rb: -------------------------------------------------------------------------------- 1 | # AdditiveWave 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Additive Wave 6 | # Create a more complex wave by adding two waves together. 7 | 8 | # Maybe better for this answer to be OOP??? 9 | MAX_WAVES = 5 10 | ARRAY_SIZE = 82 11 | ZERO_ARRAY = Array.new(ARRAY_SIZE, 0) 12 | 13 | def setup 14 | sketch_title 'Additive Wave' 15 | color_mode(RGB, 255, 255, 255, 100) 16 | @xspacing = 8 17 | @maxwaves = 5 18 | @theta = 0.0 19 | @amplitudes = (0..MAX_WAVES).map { rand(10..30) } 20 | @dx = (0..MAX_WAVES).map do 21 | period = rand(100..300) 22 | (TWO_PI / period) * @xspacing 23 | end 24 | @yvalues = Array.new(ARRAY_SIZE) 25 | end 26 | 27 | def draw 28 | background(0) 29 | calc_wave 30 | render_wave 31 | end 32 | 33 | def calc_wave 34 | # Increment theta (try different values for 'angular velocity' here 35 | @theta += 0.02 36 | # Set all height values to zero 37 | @yvalues = ZERO_ARRAY 38 | # Accumulate wave height values 39 | (0...MAX_WAVES).each do |j| 40 | x = @theta 41 | @yvalues.each_index do |i| 42 | @yvalues[i] = j.even? ? sin(x) * @amplitudes[j] : cos(x) * @amplitudes[j] 43 | x += @dx[j] 44 | end 45 | end 46 | end 47 | 48 | def render_wave 49 | # A simple way to draw the wave with an ellipse at each location 50 | no_stroke 51 | fill(255, 50) 52 | ellipse_mode(CENTER) 53 | @yvalues.each_with_index do |yvalue, x| 54 | ellipse(x * @xspacing, height / 2 + yvalue, 16, 16) 55 | end 56 | end 57 | 58 | def settings 59 | size(640, 360) 60 | end 61 | -------------------------------------------------------------------------------- /chp03_oscillation/AttractionArrayWithOscillation/AttractionArrayWithOscillation.rb: -------------------------------------------------------------------------------- 1 | # AttractionArrayWithOscillation 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Attraction Array with Oscillating objects around each thing 6 | require_relative 'attractor' 7 | require_relative 'crawler' 8 | require_relative 'oscillator' 9 | 10 | attr_reader :attractor, :crawlers 11 | 12 | # AttractionArrayWithOscillation 13 | def setup 14 | sketch_title 'Attraction Array With Oscillation' 15 | # Some random crawler bodies 16 | @crawlers = (0..4).map do 17 | Crawler.new(location: Vec2D.new(rand(width), rand(height))) 18 | end 19 | # Create an attractor body 20 | @attractor = Attractor.new( 21 | location: Vec2D.new(width / 2, height / 2), 22 | mass: 20, 23 | gravity: 0.4 24 | ) 25 | end 26 | 27 | def draw 28 | background(255) 29 | attractor.move(position: Vec2D.new(mouse_x, mouse_y)) 30 | crawlers.each do |c| 31 | attraction = attractor.attract(crawler: c) 32 | c.apply_force(force: attraction) 33 | c.update 34 | c.display 35 | end 36 | end 37 | 38 | def mouse_pressed 39 | attractor.clicked(position: Vec2D.new(mouse_x, mouse_y)) 40 | end 41 | 42 | def mouse_released 43 | attractor.stop_dragging 44 | end 45 | 46 | def settings 47 | size(640, 360) 48 | end 49 | -------------------------------------------------------------------------------- /chp03_oscillation/AttractionArrayWithOscillation/crawler.rb: -------------------------------------------------------------------------------- 1 | # AttractionArrayWithOscillation 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Attraction Array with Oscillating objects around each thing 6 | class Crawler 7 | include Processing::Proxy 8 | attr_reader :attractor, :loc, :mass, :acc, :vel 9 | 10 | def initialize(location:) 11 | @acc = Vec2D.new 12 | @vel = Vec2D.new(rand(-1.0..1), rand(-1.0..1)) 13 | @loc = location.copy 14 | @mass = rand(8.0..16) 15 | @osc = Oscillator.new(amplitude: mass * 2) 16 | end 17 | 18 | def apply_force(force:) 19 | f = force.copy 20 | f /= mass 21 | @acc += f 22 | end 23 | 24 | # Method to update location 25 | def update 26 | @vel += acc 27 | @loc += vel 28 | # Multiplying by 0 sets the all the components to 0 29 | @acc *= 0 30 | @osc.update(angle: vel.mag / 10) 31 | end 32 | 33 | # Method to display 34 | def display 35 | angle = vel.heading 36 | push_matrix 37 | translate(loc.x, loc.y) 38 | rotate(angle) 39 | ellipse_mode(CENTER) 40 | stroke(0) 41 | fill(175, 100) 42 | ellipse(0, 0, mass * 2, mass * 2) 43 | @osc.display 44 | pop_matrix 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /chp03_oscillation/AttractionArrayWithOscillation/oscillator.rb: -------------------------------------------------------------------------------- 1 | # AttractionArrayWithOscillation 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Attraction Array with Oscillating objects around each thing 6 | class Oscillator 7 | include Processing::Proxy 8 | attr_reader :amplitude, :theta 9 | 10 | def initialize(amplitude:) 11 | @theta = 0 12 | @amplitude = amplitude 13 | end 14 | 15 | def update(angle:) 16 | @theta += angle 17 | end 18 | 19 | def display 20 | x = map1d(cos(theta), (-1..1.0), (0..amplitude)) 21 | stroke(0) 22 | fill(50) 23 | line(0, 0, x, 0) 24 | ellipse(x, 0, 8, 8) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /chp03_oscillation/Exercise_3_01_exercise_baton/Exercise_3_01_exercise_baton.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | def setup 5 | sketch_title 'Exercise Baton' 6 | @angle = 0 7 | end 8 | 9 | def draw 10 | background(255) 11 | fill(127) 12 | stroke(0) 13 | rect_mode(CENTER) 14 | translate(width / 2, height / 2) 15 | rotate(@angle) 16 | line(-50, 0, 50, 0) 17 | stroke(0) 18 | stroke_weight(2) 19 | fill(127) 20 | ellipse(50, 0, 16, 16) 21 | ellipse(-50, 0, 16, 16) 22 | @angle += 0.05 23 | end 24 | 25 | def settings 26 | size(750, 150) 27 | smooth 28 | end 29 | -------------------------------------------------------------------------------- /chp03_oscillation/Exercise_3_03_cannon/Exercise_3_03_cannon.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | class CannonBall 4 | attr_reader :position, :radius, :topspeed, :velocity 5 | 6 | def initialize(location:) 7 | @position = location 8 | @velocity = Vec2D.new 9 | @acceleration = Vec2D.new 10 | @topspeed = 10 11 | @radius = 8 12 | end 13 | 14 | # Standard Euler integration 15 | def update 16 | @velocity += @acceleration 17 | velocity.set_mag(topspeed) { velocity.mag > topspeed } 18 | @position += velocity 19 | @acceleration *= 0 20 | end 21 | 22 | def apply_force(force:) 23 | @acceleration += force 24 | end 25 | 26 | def display 27 | stroke(0) 28 | stroke_weight(2) 29 | push_matrix 30 | translate(position.x, position.y) 31 | ellipse(0, 0, radius * 2, radius * 2) 32 | pop_matrix 33 | end 34 | end 35 | 36 | attr_reader :angle, :ball, :position 37 | 38 | def setup 39 | sketch_title 'Exercise Cannon' 40 | # All of this stuff should go into a Cannon class 41 | @angle = -PI / 4 42 | @position = Vec2D.new(50, 300) 43 | @shot = false 44 | @ball = CannonBall.new(location: position) 45 | end 46 | 47 | def draw 48 | background(255) 49 | push_matrix 50 | translate(position.x, position.y) 51 | rotate(angle) 52 | rect(0, -5, 50, 10) 53 | pop_matrix 54 | if @shot 55 | gravity = Vec2D.new(0, 0.2) 56 | ball.apply_force(force: gravity) 57 | ball.update 58 | end 59 | ball.display 60 | return unless ball.position.y > height || ball.position.x > width 61 | 62 | @ball = CannonBall.new(location: position) 63 | @shot = false 64 | end 65 | 66 | def key_pressed 67 | if key == CODED && key_code == RIGHT 68 | @angle += 0.1 69 | elsif key == CODED && key_code == LEFT 70 | @angle -= 0.1 71 | elsif key == ' ' 72 | @shot = true 73 | propel = Vec2D.new(Math.cos(angle), Math.sin(angle)) 74 | propel *= 10 75 | ball.apply_force(force: propel) 76 | end 77 | end 78 | 79 | def settings 80 | size(640, 360) 81 | end 82 | -------------------------------------------------------------------------------- /chp03_oscillation/Exercise_3_04_spiral/Exercise_3_04_spiral.rb: -------------------------------------------------------------------------------- 1 | # Exercise_3_04_spiral 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | attr_reader :theta, :r 5 | 6 | # A Polar coordinate, radius now starts at 0 to spiral outwards 7 | def setup 8 | sketch_title 'Exercise Spiral' 9 | background(255) 10 | @r = 0 11 | @theta = 0 12 | end 13 | 14 | def draw 15 | # Polar to Cartesian conversion 16 | x = r * cos(theta) 17 | y = r * sin(theta) 18 | # Draw an ellipse at x,y 19 | no_stroke 20 | fill(0) 21 | # Adjust for center of window 22 | ellipse(x + width / 2, y + height / 2, 16, 16) 23 | # Increment the angle 24 | @theta += 0.01 25 | # Increment the radius 26 | @r += 0.05 27 | end 28 | 29 | def settings 30 | size(750, 200) 31 | smooth 4 32 | end 33 | -------------------------------------------------------------------------------- /chp03_oscillation/Exercise_3_10_OOPWave/Exercise_3_10_OOPWave.rb: -------------------------------------------------------------------------------- 1 | # Exercise_3_10_OOPWave 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | Vect = Struct.new(:x, :y) # no functionality required no need for Vec2D here 5 | 6 | class Wave 7 | def initialize(position:, width:, amplitude:, period:) 8 | @xspacing = 8 # How far apart should each horizontal location be space 9 | @theta = 0.0 10 | @origin = position # Where does the wave's first point start 11 | @w = width # Width of entire wave 12 | @period = period # How many pixels before the wave repeats 13 | @amplitude = amplitude # Height of wave 14 | @dx = (TWO_PI / @period) * @xspacing 15 | # Use an array to store height values for the wave (not really necessary) 16 | @yvalues = Array.new(@w / @xspacing) 17 | end 18 | 19 | def calculate 20 | # Increment theta (try different values for 'angular velocity' here 21 | @theta += 0.02 22 | # For every x value, calculate a y value with sine function 23 | x = @theta 24 | @yvalues.each_index do |i| 25 | @yvalues[i] = sin(x) * @amplitude 26 | x += @dx 27 | end 28 | end 29 | 30 | def display 31 | # A simple way to draw the wave with an ellipse at each location 32 | @yvalues.each_with_index do |yvalue, idx| 33 | stroke(0) 34 | fill(0, 50) 35 | ellipse_mode(CENTER) 36 | ellipse(@origin.x + idx * @xspacing, @origin.y + yvalue, 48, 48) 37 | end 38 | end 39 | end 40 | 41 | attr_reader :wave0, :wave1 42 | 43 | # Exercise_3_10_OOPWave 44 | def setup 45 | sketch_title 'Exercise OO Wave' 46 | # Initialize a wave with starting point, width, amplitude, and period 47 | @wave0 = Wave.new( 48 | position: Vect.new(50, 75), 49 | width: 100, 50 | amplitude: 20, 51 | period: 500 52 | ) 53 | @wave1 = Wave.new( 54 | position: Vect.new(300, 100), 55 | width: 300, 56 | amplitude: 40, 57 | period: 220 58 | ) 59 | end 60 | 61 | def draw 62 | background(255) 63 | # Update and display waves 64 | wave0.calculate 65 | wave0.display 66 | wave1.calculate 67 | wave1.display 68 | end 69 | 70 | def settings 71 | size(750, 200) 72 | end 73 | -------------------------------------------------------------------------------- /chp03_oscillation/Exercise_3_11_AdditiveWave/Exercise_3_11_AdditiveWave.rb: -------------------------------------------------------------------------------- 1 | # Exercise_3_11_AdditiveWave 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Additive Wave 6 | # Create a more complex wave by adding two waves together. 7 | 8 | attr_reader :theta 9 | 10 | MAX_WAVES = 5 # total # of waves to add together 11 | 12 | def setup 13 | sketch_title 'Additive Wave Exercise' 14 | @w = width + 16 # Width of entire wave 15 | @xspacing = 8 # How far apart should each horizontal location be spaced 16 | @theta = 0.0 17 | @amplitudes = (0..MAX_WAVES).map { rand(10..30) } 18 | # @dx = Array.new(MAX_WAVES) do |x| # Value for incrementing X, to be calculated as a function of period and xspacing 19 | # period = rand(100..300) # How many pixels before the wave repeats 20 | # (TWO_PI / period) * @xspacing 21 | # end 22 | @dx = (0..MAX_WAVES).map do 23 | period = rand(100..300) # How many pixels before the wave repeats 24 | (TWO_PI / period) * @xspacing 25 | end 26 | end 27 | 28 | def draw 29 | background(255) 30 | calc_wave 31 | render_wave 32 | end 33 | 34 | def calc_wave 35 | # Increment theta (try different values for 'angular velocity' here 36 | @theta += 0.02 37 | # Set all height values to zero 38 | @yvalues = (0..(@w / @xspacing)).map { 0 } 39 | # Accumulate wave height values 40 | (0...MAX_WAVES).each do |j| 41 | x = theta 42 | @yvalues.each_index do |i| 43 | # Every other wave is cosine instead of sine 44 | @yvalues[i] += if j.even? 45 | sin(x) * @amplitudes[j] 46 | else 47 | cos(x) * @amplitudes[j] 48 | end 49 | x += @dx[j] 50 | end 51 | end 52 | end 53 | 54 | def render_wave 55 | # A simple way to draw the wave with an ellipse at each location 56 | stroke(0) 57 | fill(127, 50) 58 | ellipse_mode(CENTER) 59 | @yvalues.each_with_index { |yvalue, idx| ellipse(idx * @xspacing, height / 2 + yvalue, 48, 48) } 60 | end 61 | 62 | def settings 63 | size(750, 200) 64 | end 65 | -------------------------------------------------------------------------------- /chp03_oscillation/ExtraOscillatingUpAndDown/ExtraOscillatingUpAndDown.rb: -------------------------------------------------------------------------------- 1 | # ExtraOscillatingUpAndDown 2 | def setup 3 | sketch_title 'Extra Oscillating Up And Down' 4 | @angle = 0 5 | end 6 | 7 | def draw 8 | background(255) 9 | y = 100 * sin(@angle) 10 | @angle += 0.02 11 | fill(127) 12 | translate(width / 2, height / 2) 13 | line(0, 0, 0, y) 14 | ellipse(0, y, 16, 16) 15 | end 16 | 17 | def settings 18 | size(400, 400) 19 | end 20 | -------------------------------------------------------------------------------- /chp03_oscillation/MultipleOscillations/MultipleOscillations.rb: -------------------------------------------------------------------------------- 1 | # MultipleOscillations 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | attr_reader :angle1, :angle2, :velocity1, :velocity2 5 | 6 | def setup 7 | sketch_title 'Multiple Oscillations' 8 | @angle1 = 0 9 | @velocity1 = 0.01 10 | @amplitude1 = 300 11 | @angle2 = 0 12 | @velocity2 = 0.3 13 | @amplitude2 = 10 14 | end 15 | 16 | def draw 17 | background(255) 18 | x = 0 19 | x += @amplitude1 * cos(angle1) 20 | x += @amplitude2 * sin(angle2) 21 | @angle1 += velocity1 22 | @angle2 += velocity2 23 | ellipse_mode(CENTER) 24 | stroke(0) 25 | fill(175) 26 | translate(width / 2, height / 2) 27 | line(0, 0, x, 0) 28 | ellipse(x, 0, 20, 20) 29 | end 30 | 31 | def settings 32 | size 640, 360 33 | end 34 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_03spring_exercise_sine/NOC_03spring_exercise_sine.rb: -------------------------------------------------------------------------------- 1 | # NOC_03spring_exercise_sine 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | attr_reader :angle, :velocity 5 | 6 | def setup 7 | sketch_title 'Exercise Sine Spring' 8 | @angle = 0 9 | @velocity = 0.05 10 | end 11 | 12 | def draw 13 | background(255) 14 | x = width / 2 15 | y = map1d(sin(angle), (-1..1), (50..250)) 16 | @angle += velocity 17 | ellipse_mode(CENTER) 18 | stroke(0) 19 | fill(175) 20 | line(x, 0, x, y) 21 | ellipse(x, y, 20, 20) 22 | end 23 | 24 | def settings 25 | size(640, 360) 26 | smooth 27 | end 28 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_01_angular_motion/NOC_3_01_angular_motion.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_01_angular_motion 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | attr_reader :acceleration, :angle, :velocity 5 | 6 | def setup 7 | sketch_title 'Angular Motion' 8 | @angle = 0 9 | @velocity = 0 10 | @acceleration = 0.0001 11 | end 12 | 13 | def draw 14 | background 255 15 | fill 127 16 | stroke 0 17 | translate(width / 2, height / 2) 18 | rect_mode(CENTER) 19 | rotate(angle) 20 | stroke_weight(2) 21 | fill(127) 22 | line(-60, 0, 60, 0) 23 | ellipse(60, 0, 16, 16) 24 | ellipse(-60, 0, 16, 16) 25 | @angle += velocity 26 | @velocity += acceleration 27 | end 28 | 29 | def settings 30 | size 800, 200 31 | smooth 4 32 | end 33 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_02_forces_angular_motion/NOC_3_02_forces_angular_motion.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_02_forces_angular_motion 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'attractor' 5 | require_relative 'mover' 6 | 7 | # NOC_3_02_forces_angular_motion 8 | def setup 9 | sketch_title 'Forces Angular Motion' 10 | background(255) 11 | @movers = (0..19).map do 12 | Mover.new( 13 | mass: rand(0.1..2), 14 | location: Vec2D.new(rand(width), rand(height)) 15 | ) 16 | end 17 | @a = Attractor.new(location: Vec2D.new(width / 2, height / 2)) 18 | end 19 | 20 | def draw 21 | background(255) 22 | @a.display 23 | @movers.each do |m| 24 | attraction = @a.attract(mover: m) 25 | m.apply_force(force: attraction) 26 | m.update 27 | m.display 28 | end 29 | end 30 | 31 | def settings 32 | size(640, 360) 33 | end 34 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_02_forces_angular_motion/attractor.rb: -------------------------------------------------------------------------------- 1 | class Attractor 2 | include Processing::Proxy 3 | 4 | def initialize(location:) 5 | @location = location 6 | @mass = 20 7 | @g = 0.4 8 | end 9 | 10 | def attract(mover:) 11 | force = @location - mover.location 12 | distance = force.mag 13 | distance = constrain(distance, 5.0, 25.0) 14 | force.normalize! 15 | strength = (@g * @mass * mover.mass) / (distance * distance) 16 | force *= strength 17 | force 18 | end 19 | 20 | def display 21 | stroke(0) 22 | stroke_weight(2) 23 | fill(127) 24 | ellipse(@location.x, @location.y, 48, 48) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_02_forces_angular_motion/mover.rb: -------------------------------------------------------------------------------- 1 | class Mover 2 | include Processing::Proxy 3 | attr_reader :location, :mass 4 | 5 | def initialize(mass:, location:) 6 | @mass = mass 7 | @location = location 8 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1.0..1)) 9 | @acceleration = Vec2D.new(0, 0) 10 | @angle = 0 11 | @a_velocity = 0 12 | @a_acceleration = 0 13 | end 14 | 15 | def apply_force(force:) 16 | f = force / @mass 17 | @acceleration += f 18 | end 19 | 20 | def update 21 | @velocity += @acceleration 22 | @location += @velocity 23 | @a_acceleration = @acceleration.x / 10.0 24 | @a_velocity += @a_acceleration 25 | @a_velocity = constrain(@a_velocity, -0.1, 0.1) 26 | @angle += @a_velocity 27 | @acceleration *= 0 28 | end 29 | 30 | def display 31 | stroke(0) 32 | fill(175, 200) 33 | rect_mode(CENTER) 34 | push_matrix 35 | translate(@location.x, @location.y) 36 | rotate(@angle) 37 | rect(0, 0, @mass * 16, @mass * 16) 38 | pop_matrix 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_03_pointing_velocity/NOC_3_03_pointing_velocity.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_03_pointing_velocity 2 | # http://natureofcode.com 3 | require_relative 'mover' 4 | attr_reader :mover 5 | 6 | def setup 7 | sketch_title 'Pointing Velocity' 8 | @mover = Mover.new(location: Vec2D.new(width / 2, height / 2)) 9 | end 10 | 11 | def draw 12 | background(255) 13 | mover.update(mouse: Vec2D.new(mouse_x, mouse_y)) 14 | mover.check_edges(max_x: width, max_y: height) 15 | mover.display 16 | end 17 | 18 | def settings 19 | size(640, 360) 20 | end 21 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_03_pointing_velocity/mover.rb: -------------------------------------------------------------------------------- 1 | class Mover 2 | include Processing::Proxy 3 | attr_reader :location, :mass, :velocity 4 | 5 | def initialize(location:) 6 | @location = location 7 | @velocity = Vec2D.new 8 | @topspeed = 4 9 | @xoff = 1_000 10 | @yoff = 0 11 | end 12 | 13 | def update(mouse:) 14 | dir = mouse - location 15 | dir.normalize! 16 | dir *= 0.5 17 | @velocity += dir 18 | @velocity.set_mag(@topspeed) { velocity.mag > @topspeed } 19 | @location += velocity 20 | end 21 | 22 | def display 23 | theta = velocity.heading 24 | stroke(0) 25 | stroke_weight(2) 26 | fill(127) 27 | push_matrix 28 | rect_mode(CENTER) 29 | translate(location.x, location.y) 30 | rotate(theta) 31 | rect(0, 0, 30, 10) 32 | pop_matrix 33 | end 34 | 35 | def check_edges(max_x:, max_y:) 36 | if location.x > max_x 37 | location.x = 0 38 | elsif location.x < 0 39 | location.x = max_x 40 | end 41 | if location.y > max_y 42 | location.y = 0 43 | elsif location.y < 0 44 | location.y = max_y 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_04_PolarToCartesian_trail/NOC_3_04_PolarToCartesian_trail.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_04_PolarToCartesian_trail 2 | # PolarToCartesian 3 | # by Daniel Shiffman. 4 | # 5 | # Convert a polar coordinate (r,theta) to cartesian (x,y): 6 | # x = r * cos(theta) 7 | # y = r * sin(theta) 8 | 9 | def setup 10 | sketch_title 'Polar To Cartesian Trail' 11 | background 255 12 | # Initialize all values 13 | @r = height * 0.45 14 | @theta = 0 15 | end 16 | 17 | def draw 18 | # background(255) 19 | no_stroke 20 | fill 255, 5 21 | rect(0, 0, width, height) 22 | # Translate the origin point to the center of the screen 23 | translate(width / 2, height / 2) 24 | # Convert polar to cartesian 25 | x = @r * cos(@theta) 26 | y = @r * sin(@theta) 27 | # Draw the ellipse at the cartesian coordinate 28 | ellipse_mode(CENTER) 29 | fill 127 30 | stroke(0) 31 | stroke_weight(2) 32 | line(0, 0, x, y) 33 | ellipse(x, y, 48, 48) 34 | # Increase the angle over time 35 | @theta += 0.02 36 | end 37 | 38 | def settings 39 | size 800, 200 40 | end 41 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_05_simple_harmonic_motion/NOC_3_05_simple_harmonic_motion.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_05_simple_harmonic_motion 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | def setup 6 | sketch_title 'Simple Harmonic Motion' 7 | end 8 | 9 | def draw 10 | background 255 11 | period = 120 12 | amplitude = 300 13 | # Calculating horizontal location according to formula for simple harmonic motion 14 | x = amplitude * cos(TAU * frame_count / period) 15 | stroke(0) 16 | stroke_weight(2) 17 | fill(127) 18 | translate(width / 2, height / 2) 19 | line(0, 0, x, 0) 20 | ellipse(x, 0, 48, 48) 21 | end 22 | 23 | def settings 24 | size 800, 200 25 | end 26 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_06_simple_harmonic_motion/NOC_3_06_simple_harmonic_motion.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_06_simple_harmonic_motion 2 | 3 | attr_reader :angle, :velocity 4 | 5 | def setup 6 | sketch_title 'Simple Harmonic Motion' 7 | @angle = 0 8 | @velocity = 0.03 9 | end 10 | 11 | def draw 12 | background 255 13 | amplitude = 300 14 | x = amplitude * cos(angle) 15 | @angle += velocity 16 | ellipse_mode(CENTER) 17 | stroke 0 18 | fill 175 19 | translate(width / 2, height / 2) 20 | line(0, 0, x, 0) 21 | ellipse(x, 0, 20, 20) 22 | end 23 | 24 | def settings 25 | size 640, 360 26 | smooth 4 27 | end 28 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_07_oscillating_objects/NOC_3_07_oscillating_objects.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_07_oscillating_objects 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | require_relative 'oscillator' 6 | attr_reader :oscillators 7 | 8 | def setup 9 | sketch_title 'Oscillating Objects' 10 | @oscillators = Array.new(10) { Oscillator.new(max_x: width, max_y: height) } 11 | end 12 | 13 | def draw 14 | background 255 15 | oscillators.each do |o| 16 | o.oscillate 17 | o.display 18 | end 19 | end 20 | 21 | def settings 22 | size 800, 200 23 | smooth 4 24 | end 25 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_07_oscillating_objects/oscillator.rb: -------------------------------------------------------------------------------- 1 | class Oscillator 2 | include Processing::Proxy 3 | 4 | attr_reader :angle, :amplitude, :velocity, :width, :height 5 | 6 | def initialize(max_x:, max_y:) 7 | @width = max_x 8 | @height = max_y 9 | @angle = Vec2D.new 10 | @velocity = Vec2D.new(rand(-0.05..0.05), rand(-0.05..0.05)) 11 | @amplitude = Vec2D.new(rand(20..width / 2), rand(20..height / 2)) 12 | end 13 | 14 | def oscillate 15 | @angle += velocity 16 | end 17 | 18 | def display 19 | x = sin(angle.x) * amplitude.x 20 | y = sin(angle.y) * amplitude.y 21 | push_matrix 22 | translate(width / 2, height / 2) 23 | stroke(0) 24 | stroke_weight(2) 25 | fill(127, 127) 26 | line(0, 0, x, y) 27 | ellipse(x, y, 32, 32) 28 | pop_matrix 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_08_static_wave_lines/NOC_3_08_static_wave_lines.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_08_static_wave_lines 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | angle = 0 5 | angle_vel = 0.1 6 | 7 | sketch_title 'Static Wave Lines' 8 | # resizable 9 | background(255) 10 | stroke(0) 11 | stroke_weight(2) 12 | no_fill 13 | begin_shape 14 | (0..width).step(5) do |x| 15 | y = map1d(sin(angle), (-1..1.0), (0..height)) 16 | vertex(x, y) 17 | angle += angle_vel 18 | end 19 | end_shape 20 | 21 | # sketch_size 800, 200 22 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_09_exercise_additive_wave/NOC_3_09_exercise_additive_wave.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_09_exercise_additive_wave 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | # Additive Wave 6 | # Create a more complex wave by adding two waves together. 7 | 8 | # Maybe better for this answer to be OOP??? 9 | 10 | attr_reader :dx, :amplitude, :max_waves, :y_values 11 | 12 | def setup 13 | sketch_title 'Additive Wave Exercise' 14 | @max_waves = 5 # Total number of waves to add together 15 | @wave_width = width + 16 # Width of entire wave 16 | @x_spacing = 8 # How far apart should each horizontal location be spaced 17 | @theta = 0.0 18 | @amplitude = [] # Height of wave 19 | @dx = [] # Value for incrementing X, to be calculated as a function of period and x_spacing 20 | @max_waves.times do |_i| 21 | amplitude << rand(10..30) 22 | period = rand(100..300) # How many pixels before the wave repeats 23 | dx << (TAU / period) * @x_spacing 24 | end 25 | frame_rate 30 26 | color_mode RGB, 255, 255, 255, 100 27 | end 28 | 29 | def draw 30 | background 0 31 | calculate_wave 32 | render_wave 33 | end 34 | 35 | def calculate_wave 36 | # Increment theta (try different values for 'angular velocity' here 37 | @theta += 0.02 38 | # Set all height values to zero 39 | @y_values = Array.new(@wave_width / @x_spacing, 0) 40 | # Accumulate wave height values 41 | max_waves.times do |j| 42 | x = @theta 43 | y_values.length.times do |i| 44 | # Every other wave is cosine instead of sine 45 | value = j.even? ? sin(x) : cos(x) 46 | y_values[i] += value * amplitude[j] 47 | x += dx[j] 48 | end 49 | end 50 | end 51 | 52 | def render_wave 53 | # A simple way to draw the wave with an ellipse at each location 54 | no_stroke 55 | fill 255, 50 56 | ellipse_mode CENTER 57 | y_values.each_with_index do |y, i| 58 | ellipse i * @x_spacing, height / 2 + y, 16, 16 59 | end 60 | end 61 | 62 | def settings 63 | size 640, 360 64 | end 65 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_09_wave/NOC_3_09_wave.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_09_wave 2 | def setup 3 | sketch_title 'Noc 3 09 Wave' 4 | @start_angle = 0 5 | @angle_vel = 0.23 6 | end 7 | 8 | def draw 9 | background(255) 10 | 11 | @start_angle += 0.015 12 | angle = @start_angle 13 | 14 | (0..width).step(24) do |x| 15 | y = map1d(sin(angle), (-1..1), (0..height)) 16 | stroke(0) 17 | fill(0, 50) 18 | stroke_weight(2) 19 | ellipse(x, y, 48, 48) 20 | angle += @angle_vel 21 | end 22 | end 23 | 24 | def settings 25 | size(800, 200) 26 | smooth 27 | end 28 | -------------------------------------------------------------------------------- /chp03_oscillation/NOC_3_10_PendulumExampleSimplified/NOC_3_10_PendulumExampleSimplified.rb: -------------------------------------------------------------------------------- 1 | # NOC_3_10_PendulumExampleSimplified 2 | 3 | class Pendulum 4 | attr_reader :angle, :origin, :location 5 | 6 | def initialize(origin_, r_) 7 | @origin = origin_ 8 | @location = Vec2D.new 9 | @r = r_ # length of arm 10 | @angle = PI / 4 11 | @aVelocity = 0.0 12 | @aAcceleration = 0.0 13 | @damping = 0.995 # Arbitrary damping 14 | end 15 | 16 | def go 17 | update 18 | display 19 | end 20 | 21 | def update 22 | gravity = 0.4 # Arbitrary constant 23 | # Calculate acceleration (see: http://www.myphysicslab.com/pendulum1.html) 24 | @aAcceleration = (-1 * gravity / @r) * sin(angle) 25 | @aVelocity += @aAcceleration # Increment velocity 26 | @aVelocity *= @damping # Arbitrary damping 27 | @angle += @aVelocity # Increment angle 28 | end 29 | 30 | def display 31 | # Polar to cartesian conversion 32 | @location = Vec2D.new(@r * sin(angle), @r * cos(angle)) 33 | @location += origin # Set the location is relative to the pendulum's origin 34 | stroke(0) 35 | stroke_weight(2) 36 | # Draw the arm 37 | line(origin.x, origin.y, location.x, location.y) 38 | ellipse_mode(CENTER) 39 | fill(175) 40 | # Draw the ball 41 | ellipse(location.x, location.y, 48, 48) 42 | end 43 | end 44 | 45 | # NOC_3_10_PendulumExampleSimplified 46 | def setup 47 | sketch_title 'Pendulum Example Simplified' 48 | # Make a new Pendulum with an origin location and armlength 49 | @p = Pendulum.new(Vec2D.new(width / 2, 0), 175) 50 | end 51 | 52 | def draw 53 | background(255) 54 | @p.go 55 | end 56 | 57 | def settings 58 | size(800, 200) 59 | smooth 4 60 | end 61 | -------------------------------------------------------------------------------- /chp03_oscillation/OOPWaveParticles/OOPWaveParticles.rb: -------------------------------------------------------------------------------- 1 | # OOPWaveParticles 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | class Particle 5 | attr_reader :location 6 | 7 | def initialize(location:) 8 | @location = location 9 | end 10 | 11 | def new_location(position:) 12 | @location = position 13 | end 14 | 15 | def display 16 | fill(rand(255)) 17 | ellipse(location.x, location.y, 16, 16) 18 | end 19 | end 20 | 21 | Vect = Struct.new(:x, :y) # no fancy behaviour reqd we can use a struct here 22 | 23 | # The Wave class 24 | class Wave 25 | attr_reader :origin 26 | 27 | def initialize(origin:, width:, amplitude:, period:) 28 | @origin = origin 29 | @w = width 30 | @period = period 31 | @amplitude = amplitude 32 | @xspacing = 8 33 | @dx = (TWO_PI / @period) * @xspacing 34 | @theta = 0.0 35 | @particles = (0..@w / @xspacing).map { Particle.new(location: Vec2D.new) } 36 | end 37 | 38 | def calculate 39 | # Increment theta (try different values for 'angular velocity' here 40 | @theta += 0.02 41 | # For every x value, calculate a y value with sine function 42 | x = @theta 43 | @particles.each_index do |i| 44 | @particles[i].new_location( 45 | position: Vec2D.new( 46 | origin.x + i * @xspacing, 47 | origin.y + sin(x) * @amplitude 48 | ) 49 | ) 50 | x += @dx 51 | end 52 | end 53 | 54 | def display 55 | # A simple way to draw the wave with an ellipse at each location 56 | @particles.each(&:display) 57 | end 58 | end 59 | 60 | attr_reader :waves 61 | 62 | def setup 63 | sketch_title 'OO Wave Particles' 64 | wave0 = Wave.new( 65 | origin: Vect.new(200, 75), 66 | width: 100, 67 | amplitude: 20, 68 | period: 500 69 | ) 70 | wave1 = Wave.new( 71 | origin: Vect.new(150, 250), 72 | width: 300, 73 | amplitude: 40, 74 | period: 220 75 | ) 76 | @waves = [wave0, wave1] 77 | end 78 | 79 | def draw 80 | background(255) 81 | # Update and display waves 82 | waves.each do |wave| 83 | wave.calculate 84 | wave.display 85 | end 86 | end 87 | 88 | def settings 89 | size(640, 360) 90 | end 91 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_01_SingleParticle/NOC_4_01_SingleParticle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | # Simple Particle System 5 | # A simple Particle class 6 | 7 | class Particle 8 | attr_reader :acceleration, :lifespan, :location, :velocity 9 | 10 | def initialize(location:) 11 | @acceleration = Vec2D.new(0, 0.05) 12 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1.0..1)) 13 | @location = location 14 | @lifespan = 255.0 15 | end 16 | 17 | def run 18 | update 19 | display 20 | end 21 | 22 | # Method to update location 23 | def update 24 | @velocity += acceleration 25 | @location += velocity 26 | @lifespan -= 2.0 27 | end 28 | 29 | # Method to display 30 | def display 31 | stroke(0, lifespan) 32 | stroke_weight(2) 33 | fill(127, lifespan) 34 | ellipse(location.x, location.y, 12, 12) 35 | end 36 | 37 | # Is the particle still useful? 38 | def dead? 39 | lifespan < 0.0 40 | end 41 | end 42 | 43 | attr_reader :p 44 | 45 | def setup 46 | sketch_title 'Single Particle' 47 | @p = Particle.new(location: Vec2D.new(width / 2, 20)) 48 | background(255) 49 | end 50 | 51 | def draw 52 | background(255) 53 | p.run 54 | @p = Particle.new(location: Vec2D.new(width / 2, 20)) if p.dead? 55 | end 56 | 57 | def settings 58 | size(800, 200) 59 | smooth 4 60 | end 61 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_01_SingleParticle_trail/NOC_4_01_SingleParticle_trail.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | # Simple Particle System 5 | # A simple Particle class 6 | 7 | require_relative 'particle' 8 | 9 | def setup 10 | sketch_title 'Single Particle Trail' 11 | @p = Particle.new(location: Vec2D.new(width / 2, 20)) 12 | background(255) 13 | end 14 | 15 | def draw 16 | # NB: in ruby-processing use mouse_pressed? instead of mousePressed 17 | # replacing a conditional with a guard clause 18 | return unless mouse_pressed? 19 | 20 | no_stroke 21 | fill(255, 5) 22 | rect(0, 0, width, height) 23 | @p.run 24 | end 25 | 26 | def settings 27 | size(800, 200) 28 | smooth 4 29 | end 30 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_01_SingleParticle_trail/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | # A simple Particle class 5 | 6 | class Particle 7 | include Processing::Proxy 8 | 9 | attr_reader :acceleration, :lifespan, :location, :velocity 10 | 11 | def initialize(location:) 12 | @acceleration = Vec2D.new(0, 0.05) 13 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1.0..0)) 14 | @location = location 15 | @lifespan = 255 16 | end 17 | 18 | def run 19 | update 20 | display 21 | puts 'Particle dead!' if lifespan < 0 22 | end 23 | 24 | # Method to update location 25 | def update 26 | @velocity += acceleration 27 | @location += velocity 28 | @lifespan -= 2 29 | end 30 | 31 | # Method to display 32 | def display 33 | stroke(0, lifespan) 34 | stroke_weight(2) 35 | fill(127, lifespan) 36 | ellipse(location.x, location.y, 12, 12) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_02_ArrayListParticles/NOC_4_02_ArrayListParticles.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | # Simple Particle System 5 | # A simple Particle class 6 | require_relative 'particle' 7 | attr_reader :particles 8 | 9 | def setup 10 | sketch_title 'Array Of Particles' 11 | @particles = [] 12 | end 13 | 14 | def draw 15 | background(255) 16 | particles << Particle.new(location: Vec2D.new(width / 2, 50)) 17 | particles.each(&:run) 18 | particles.reject!(&:dead?) 19 | end 20 | 21 | def settings 22 | size(640, 360) 23 | end 24 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_02_ArrayListParticles/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # http://natureofcode.com 3 | 4 | # Simple Particle System 5 | # A simple Particle class 6 | 7 | class Particle 8 | include Processing::Proxy 9 | def initialize(location:) 10 | @acceleration = Vec2D.new(0, 0.05) 11 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1.0..0)) 12 | @location = location 13 | @lifespan = 255.0 14 | end 15 | 16 | def run 17 | update 18 | display 19 | end 20 | 21 | # Method to update location 22 | def update 23 | @velocity += @acceleration 24 | @location += @velocity 25 | @lifespan -= 2.0 26 | end 27 | 28 | # Method to display 29 | def display 30 | stroke(0, @lifespan) 31 | stroke_weight(2) 32 | fill(127, @lifespan) 33 | ellipse(@location.x, @location.y, 12, 12) 34 | end 35 | 36 | # Is the particle still useful? 37 | def dead? 38 | @lifespan < 0.0 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_03_ParticleSystemClass/NOC_4_03_ParticleSystemClass.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_03_ParticleSystemClass 2 | require_relative 'particle_system' 3 | 4 | def setup 5 | sketch_title 'Particle System Class' 6 | @particle_system = ParticleSystem.new(origin: Vec2D.new(width / 2, 50)) 7 | end 8 | 9 | def draw 10 | background(255) 11 | @particle_system.add_particle 12 | @particle_system.run 13 | end 14 | 15 | def settings 16 | size(640, 360) 17 | end 18 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_03_ParticleSystemClass/particle.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_03_ParticleSystemClass 2 | 3 | class Particle 4 | include Processing::Proxy 5 | attr_reader :acceleration, :lifespan, :location, :velocity 6 | 7 | def initialize(location:) 8 | @location = location 9 | @acceleration = Vec2D.new(0, 0.05) 10 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1..0)) 11 | @lifespan = 255.0 12 | end 13 | 14 | def run 15 | update 16 | display 17 | end 18 | 19 | # Method to update location 20 | def update 21 | @velocity += acceleration 22 | @location += velocity 23 | @lifespan -= 2.0 24 | end 25 | 26 | # Method to display 27 | def display 28 | stroke(0, lifespan) 29 | stroke_weight(2) 30 | fill(127, lifespan) 31 | ellipse(location.x, location.y, 12, 12) 32 | end 33 | 34 | # Is the particle still useful? 35 | def dead? 36 | lifespan < 0.0 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_03_ParticleSystemClass/particle_system.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_03_ParticleSystemClass 2 | require 'forwardable' 3 | require_relative 'particle' 4 | 5 | module Runnable 6 | def run 7 | reject!(&:dead?) 8 | each(&:run) 9 | end 10 | end 11 | 12 | class ParticleSystem 13 | include Runnable 14 | include Enumerable 15 | extend Forwardable 16 | def_delegators(:@particles, :reject!, :<<, :each) 17 | attr_reader :origin 18 | 19 | def initialize(origin:) 20 | @origin = origin 21 | @particles = [] 22 | end 23 | 24 | def add_particle 25 | self << Particle.new(location: origin) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_05_ParticleSystemInheritancePolymorphism/NOC_4_05_ParticleSystemInheritancePolymorphism.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_05_ParticleSystemInheritancePolymorphism 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'particle_system' 5 | 6 | def setup 7 | sketch_title 'Noc 4 05 Particle System Inheritance Polymorphism' 8 | @particle_system = ParticleSystem.new(origin: Vec2D.new(width / 2, 50)) 9 | end 10 | 11 | def draw 12 | background(255) 13 | @particle_system.add_particle 14 | @particle_system.run 15 | end 16 | 17 | def settings 18 | size(640, 360) 19 | end 20 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_05_ParticleSystemInheritancePolymorphism/particle.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_05_ParticleSystemInheritancePolymorphism 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'particle_system' 5 | require_relative 'particle' 6 | 7 | class Particle 8 | include Processing::Proxy 9 | attr_reader :acceleration, :lifespan, :location, :velocity 10 | 11 | def initialize(location:) 12 | @acceleration = Vec2D.new(0, 0.05) 13 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1..0)) 14 | @location = location 15 | @lifespan = 255.0 16 | end 17 | 18 | def run 19 | update 20 | display 21 | end 22 | 23 | # Method to update location 24 | def update 25 | @velocity += @acceleration 26 | @location += @velocity 27 | @lifespan -= 2.0 28 | end 29 | 30 | # Method to display 31 | def display 32 | stroke(0, @lifespan) 33 | stroke_weight(2) 34 | fill(127, @lifespan) 35 | ellipse(@location.x, @location.y, 12, 12) 36 | end 37 | 38 | # Is the particle still useful? 39 | def dead? 40 | @lifespan < 0.0 41 | end 42 | end 43 | 44 | class Confetti < Particle 45 | # NB: inherits initialize from Particle no need for super here 46 | 47 | def display 48 | rect_mode(CENTER) 49 | fill(127, lifespan) 50 | stroke(0, lifespan) 51 | stroke_weight(2) 52 | push_matrix 53 | translate(location.x, location.y) 54 | theta = map1d(location.x, (0..640), (0..TAU * 2)) 55 | rotate(theta) 56 | rect(0, 0, 12, 12) 57 | pop_matrix 58 | end 59 | end 60 | 61 | def setup 62 | sketch_title 'Particle' 63 | @particle_system = ParticleSystem.new(origin: Vec2D.new(width / 2, 50)) 64 | end 65 | 66 | def draw 67 | background(255) 68 | @particle_system.add_particle 69 | @particle_system.run 70 | end 71 | 72 | def settings 73 | size(640, 360) 74 | end 75 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_05_ParticleSystemInheritancePolymorphism/particle_system.rb: -------------------------------------------------------------------------------- 1 | require_relative 'particle' 2 | require 'forwardable' 3 | 4 | module Runnable 5 | def run 6 | reject!(&:dead?) 7 | each(&:run) 8 | end 9 | end 10 | 11 | class ParticleSystem 12 | extend Forwardable 13 | def_delegators(:@particles, :reject!, :<<, :each) 14 | include Runnable 15 | include Enumerable 16 | 17 | def initialize(origin:) 18 | @origin = origin 19 | @particles = [] 20 | end 21 | 22 | def add_particle 23 | part = rand < 0.5 ? Particle.new(location: @origin) : Confetti.new(location: @origin) 24 | self << part 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_06_ParticleSystemForces/NOC_4_06_ParticleSystemForces.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_06_ParticleSystemForces 2 | 3 | require_relative 'particle_system' 4 | attr_reader :ps 5 | 6 | def setup 7 | sketch_title 'Particle System Forces' 8 | @ps = ParticleSystem.new(origin: Vec2D.new(width / 2, 50)) 9 | end 10 | 11 | def draw 12 | background(255) 13 | # Apply gravity force to all Particles 14 | gravity = Vec2D.new(0, 0.1) 15 | ps.apply_force(force: gravity) 16 | ps.add_particle 17 | ps.run 18 | end 19 | 20 | def settings 21 | size(640, 360) 22 | end 23 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_06_ParticleSystemForces/particle.rb: -------------------------------------------------------------------------------- 1 | class Particle 2 | include Processing::Proxy 3 | attr_reader :acceleration, :lifespan, :location, :velocity 4 | 5 | def initialize(location:) 6 | @acceleration = Vec2D.new(0, 0) 7 | @velocity = Vec2D.new(rand(-1.0..1), rand(-2..0)) 8 | @location = location 9 | @lifespan = 255.0 10 | @mass = 1 11 | end 12 | 13 | def run 14 | update 15 | display 16 | end 17 | 18 | def apply_force(force:) 19 | f = force / @mass 20 | @acceleration += f 21 | end 22 | 23 | # Method to update location 24 | def update 25 | @velocity += acceleration 26 | @location += velocity 27 | @acceleration *= 0 28 | @lifespan -= 2.0 29 | end 30 | 31 | # Method to display 32 | def display 33 | stroke(0, lifespan) 34 | stroke_weight(2) 35 | fill(127, lifespan) 36 | ellipse(location.x, location.y, 12, 12) 37 | end 38 | 39 | # Is the particle still useful? 40 | def dead? 41 | lifespan < 0.0 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_06_ParticleSystemForces/particle_system.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require_relative 'particle' 3 | 4 | module Runnable 5 | def run 6 | reject!(&:dead?) 7 | each(&:run) 8 | end 9 | end 10 | 11 | class ParticleSystem 12 | include Runnable 13 | include Enumerable 14 | extend Forwardable 15 | def_delegators(:@particle_system, :each, :<<, :reject!) 16 | 17 | attr_reader :origin 18 | 19 | def initialize(origin:) 20 | @origin = origin 21 | @particle_system = [] 22 | end 23 | 24 | def add_particle 25 | self << Particle.new(location: origin) 26 | end 27 | 28 | def apply_force(force:) 29 | each { |p| p.apply_force(force: force) } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_07_ParticleSystemForcesRepeller/NOC_4_07_ParticleSystemForcesRepeller.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_07_ParticleSystemForcesRepeller 2 | # http://natureofcode.com 3 | require_relative 'repeller' 4 | require_relative 'particle_system' 5 | 6 | attr_reader :ps, :repeller 7 | 8 | def setup 9 | sketch_title 'Particle System Forces Repeller' 10 | @ps = ParticleSystem.new(origin: Vec2D.new(width / 2, 50)) 11 | @repeller = Repeller.new(origin: Vec2D.new(width / 2 - 20, height / 2)) 12 | end 13 | 14 | def draw 15 | background(255) 16 | ps.add_particle 17 | gravity = Vec2D.new(0, 0.1) 18 | ps.apply_force(force: gravity) 19 | ps.apply_repeller(repel: repeller) 20 | repeller.display 21 | ps.run 22 | end 23 | 24 | def settings 25 | size(640, 360) 26 | end 27 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_07_ParticleSystemForcesRepeller/particle.rb: -------------------------------------------------------------------------------- 1 | class Particle 2 | include Processing::Proxy 3 | 4 | attr_reader :acceleration, :lifespan, :location, :velocity 5 | 6 | def initialize(location:) 7 | @acceleration = Vec2D.new(0, 0) 8 | @velocity = Vec2D.new(rand(-1.0..1), rand(-2.0...0)) 9 | @location = location 10 | @lifespan = 255 11 | @mass = 1 12 | end 13 | 14 | def run 15 | update 16 | display 17 | end 18 | 19 | def apply_force(force:) 20 | f = force / @mass 21 | @acceleration += f 22 | end 23 | 24 | # Method to update location 25 | def update 26 | @velocity += acceleration 27 | @location += velocity 28 | @acceleration *= 0 29 | @lifespan -= 2 30 | end 31 | 32 | # Method to display 33 | def display 34 | stroke(0, lifespan) 35 | stroke_weight(2) 36 | fill(127, lifespan) 37 | ellipse(location.x, location.y, 12, 12) 38 | end 39 | 40 | # Is the particle still useful? 41 | def dead? 42 | lifespan < 0.0 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_07_ParticleSystemForcesRepeller/particle_system.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require_relative 'particle' 3 | 4 | module Runnable 5 | def run 6 | reject!(&:dead?) 7 | each(&:run) 8 | end 9 | end 10 | 11 | class ParticleSystem 12 | include Runnable 13 | include Enumerable 14 | extend Forwardable 15 | def_delegators(:@particle_system, :each, :<<, :reject!) 16 | 17 | def initialize(origin:) 18 | @origin = origin 19 | @particle_system = [] 20 | end 21 | 22 | def add_particle 23 | self << Particle.new(location: @origin) 24 | end 25 | 26 | def apply_force(force:) 27 | each { |p| p.apply_force(force: force) } 28 | end 29 | 30 | def apply_repeller(repel:) 31 | each do |p| 32 | f = repel.repel_force(particle: p) 33 | p.apply_force(force: f) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_07_ParticleSystemForcesRepeller/repeller.rb: -------------------------------------------------------------------------------- 1 | # The repeller instance repels particles 2 | class Repeller 3 | include Processing::Proxy 4 | 5 | G = 100 6 | attr_reader :location 7 | 8 | def initialize(origin:) 9 | @location = origin 10 | end 11 | 12 | def display 13 | stroke(0) 14 | stroke_weight(2) 15 | fill(175) 16 | ellipse(location.x, location.y, 48, 48) 17 | end 18 | 19 | def repel_force(particle:) 20 | dir = location - particle.location # Calculate direction of force 21 | d = dir.mag # Distance between objects 22 | dir.normalize! # Normalize vector for direction 23 | d = constrain(d, 5.0, 100.0) # Keep distance within a range 24 | # Repelling force is inversely proportional to distance 25 | force = -1 * G / (d * d) 26 | dir * force # Get force vector --> magnitude * direction 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_08_ParticleSystemSmoke_b/NOC_4_08_ParticleSystemSmoke_b.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_08_ParticleSystemSmoke_b 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'particle_system' 5 | 6 | attr_reader :ps 7 | 8 | def setup 9 | sketch_title 'Noc 4 08 Particle System Smoke B' 10 | img = load_image(data_path('texture.png')) 11 | @ps = ParticleSystem.new( 12 | number: 0, 13 | origin: Vec2D.new(width / 2, height - 75), 14 | image: img 15 | ) 16 | end 17 | 18 | def draw 19 | background(0) 20 | # Calculate a "wind" force based on mouse horizontal position 21 | dx = map1d(mouse_x, (0..width), (-0.2..0.2)) 22 | wind = Vec2D.new(dx, 0) 23 | ps.apply_force(force: wind) 24 | ps.run 25 | 2.times { ps.add_particle } 26 | # Draw an arrow representing the wind force 27 | draw_vector(wind, Vec2D.new(width / 2, 50), 500) 28 | end 29 | 30 | # Renders a vector object 'v' as an arrow and a location 'loc' 31 | def draw_vector(v, loc, scayl) 32 | push_matrix 33 | arrowsize = 4 34 | # Translate to location to render vector 35 | translate(loc.x, loc.y) 36 | stroke(255) 37 | # Call vector heading function to get direction (note that pointing up is a heading of 0) and rotate 38 | rotate(v.heading) 39 | # Calculate length of vector & scale it to be bigger or smaller if necessary 40 | len = v.mag * scayl 41 | # Draw three lines to make an arrow (draw pointing up since we've rotate to the proper direction) 42 | line(0, 0, len, 0) 43 | line(len, 0, len - arrowsize, arrowsize / 2) 44 | line(len, 0, len - arrowsize, -arrowsize / 2) 45 | pop_matrix 46 | end 47 | 48 | def settings 49 | size(640, 360) 50 | smooth 4 51 | end 52 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_08_ParticleSystemSmoke_b/data/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-processing/The-Nature-of-Code-for-JRubyArt/119f42afc472d80aae8fde49cfaaf379681b7cff/chp04_systems/NOC_4_08_ParticleSystemSmoke_b/data/texture.png -------------------------------------------------------------------------------- /chp04_systems/NOC_4_08_ParticleSystemSmoke_b/particle.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_08_ParticleSystemSmoke_b 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | class Particle 5 | include Processing::Proxy 6 | attr_reader :lifespan 7 | 8 | def initialize(loc, vel, img) 9 | @loc = loc.copy 10 | @img = img 11 | @vel = vel 12 | @acc = Vec2D.new 13 | @lifespan = 100 14 | end 15 | 16 | def run 17 | update 18 | render 19 | end 20 | 21 | def apply_force(force) 22 | @acc += force 23 | end 24 | 25 | def update 26 | @vel += @acc 27 | @loc += @vel 28 | @lifespan -= 2.5 29 | @acc *= 0 30 | end 31 | 32 | def render 33 | image_mode(CENTER) 34 | tint(255, lifespan) 35 | image(@img, @loc.x, @loc.y) 36 | # Drawing a circle instead 37 | # fill(255, @lifespan) 38 | # no_stroke 39 | # ellipse(@loc.x, @loc.y, 10, 10) 40 | end 41 | 42 | def dead? 43 | lifespan <= 0.0 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_08_ParticleSystemSmoke_b/particle_system.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_08_ParticleSystemSmoke_b 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | require 'forwardable' 6 | require_relative 'particle' 7 | 8 | module Runnable 9 | def run 10 | reject!(&:dead?) 11 | each(&:run) 12 | end 13 | end 14 | 15 | class ParticleSystem 16 | include Runnable 17 | include Enumerable 18 | extend Forwardable 19 | def_delegators(:@particles, :reject!, :<<, :each, :empty?) 20 | def_delegator(:@particles, :empty?, :dead?) 21 | 22 | def initialize(number:, origin:, image:) 23 | @origin = origin 24 | @img = image 25 | # avoid confusion with ruby Random 26 | @generator = Java::JavaUtil::Random.new 27 | @particles = Array.new(number) { create_particle } 28 | end 29 | 30 | def add_particle(particle = nil) 31 | particle ||= create_particle 32 | self << particle 33 | end 34 | 35 | def apply_force(force:) 36 | each { |p| p.apply_force(force) } 37 | end 38 | 39 | private 40 | 41 | def create_particle 42 | vx = @generator.next_gaussian * 0.3 43 | vy = @generator.next_gaussian * 0.3 - 1 44 | vel = Vec2D.new(vx, vy) 45 | Particle.new(@origin, vel, @img) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_09_AdditiveBlending/NOC_4_09_AdditiveBlending.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_09_AdditiveBlending 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | require_relative 'particle_system' 5 | attr_reader :ps 6 | 7 | def setup 8 | sketch_title 'Additive Blending' 9 | img = load_image(data_path('texture.png')) 10 | @ps = ParticleSystem.new(number: 0, origin: Vec2D.new(width / 2, 50), image: img) 11 | end 12 | 13 | def draw 14 | blend_mode(ADD) 15 | background(0) 16 | ps.run 17 | 10.times { ps.add_particle } 18 | end 19 | 20 | def settings 21 | size(640, 340, P2D) 22 | end 23 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_09_AdditiveBlending/data/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ruby-processing/The-Nature-of-Code-for-JRubyArt/119f42afc472d80aae8fde49cfaaf379681b7cff/chp04_systems/NOC_4_09_AdditiveBlending/data/texture.png -------------------------------------------------------------------------------- /chp04_systems/NOC_4_09_AdditiveBlending/particle.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_09_AdditiveBlending 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | class Particle 6 | include Processing::Proxy 7 | attr_reader :acceleration, :lifespan, :location, :velocity 8 | 9 | def initialize(location:, image:) 10 | @acceleration = Vec2D.new(0, 0.05) 11 | @velocity = Vec2D.new(rand(-1.0..1), rand(-1.0..0)) 12 | @velocity *= 2 13 | @location = location 14 | @img = image 15 | @lifespan = 255.0 16 | end 17 | 18 | def run 19 | update 20 | render 21 | end 22 | 23 | # Method to update location 24 | def update 25 | @velocity += acceleration 26 | @location += velocity 27 | @lifespan -= 2.0 28 | end 29 | 30 | # Method to display 31 | def render 32 | image_mode(CENTER) 33 | tint(lifespan) 34 | image(@img, location.x, location.y) 35 | end 36 | 37 | # Is the particle still useful? 38 | def dead? 39 | lifespan < 0.0 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /chp04_systems/NOC_4_09_AdditiveBlending/particle_system.rb: -------------------------------------------------------------------------------- 1 | # NOC_4_09_AdditiveBlending 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | 5 | require 'forwardable' 6 | require_relative 'particle' 7 | 8 | # The runnable module 9 | module Runnable 10 | def run 11 | reject!(&:dead?) 12 | each(&:run) 13 | end 14 | end 15 | 16 | # The ParticleSystem class doubles as a enumerator and is runnable 17 | class ParticleSystem 18 | include Runnable 19 | include Enumerable 20 | include Processing::Proxy 21 | extend Forwardable 22 | def_delegators(:@particles, :reject!, :<<, :each, :empty) 23 | def_delegator(:@particles, :empty?, :dead?) 24 | 25 | attr_reader :img, :origin 26 | 27 | def initialize(number:, origin:, image:) 28 | @origin = origin 29 | @img = image 30 | @particles = (0..number).map { Particle.new(location: origin, image: img) } 31 | end 32 | 33 | def add_particle(obj = nil) 34 | obj ||= Particle.new(location: origin, image: img) 35 | self << obj 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /chp05_physicslibraries/CollisionsEqualMass/collisions_equal_mass.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Collisions -- Elastic, Equal Mass, Two objects only 6 | 7 | # Based off of Chapter 9: Resolving Collisions 8 | # Mathematics and Physics for Programmers by Danny Kodicek 9 | 10 | # A Thing class for idealized collisions 11 | 12 | require_relative 'mover' 13 | 14 | attr_reader :a, :b, :show_vectors 15 | 16 | def setup 17 | sketch_title 'Collisions Equal Mass' 18 | @a = Mover.new( 19 | app: self, 20 | velocity: Vec2D.new(rand(5.0), rand(-5..5.0)), 21 | location: Vec2D.new(10, 10) 22 | ) 23 | @b = Mover.new( 24 | app: self, 25 | velocity: Vec2D.new(-2, 1), 26 | location: Vec2D.new(150, 150) 27 | ) 28 | @show_vectors = true 29 | end 30 | 31 | def draw 32 | background 255 33 | a.go 34 | b.go 35 | # Note this function will ONLY WORK with two objects 36 | # Needs to be revised in the case of an array of objects 37 | a.collide_equal_mass(b) 38 | end 39 | 40 | def mouse_pressed 41 | @show_vectors = !show_vectors 42 | end 43 | 44 | def settings 45 | size(200, 200) 46 | end 47 | -------------------------------------------------------------------------------- /chp05_physicslibraries/CollisionsEqualMass/world.rb: -------------------------------------------------------------------------------- 1 | # Class provides an OO way constraining a Mover in a 2D space 2 | # use 3 | # world = World.new((0..width), (0..height)) 4 | # world.constrain_mover(mover) 5 | class World 6 | include MathTool # avoid including all Processing::Proxy to use constrain 7 | attr_reader :xrange, :yrange 8 | 9 | def initialize(xrange, yrange) 10 | @xrange = xrange 11 | @yrange = yrange 12 | end 13 | 14 | # @param mover is expected respond to loc, vel 15 | # that in turn respond to x and y getter/setters (Vec2D does this) 16 | 17 | def constrain_mover(mover) 18 | # Note clip functionality, extends Range in ruby-processing 19 | unless xrange.cover? mover.loc.x 20 | mover.vel.x *= -1 21 | mover.loc.x = constrain(mover.loc.x, xrange.begin, xrange.last) 22 | end 23 | return if yrange.cover? mover.loc.y 24 | 25 | mover.vel.y *= -1 26 | mover.loc.y = constrain(mover.loc.y, yrange.begin, yrange.last) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/bumpy_surface_noise.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # PBox2D example 3 | # An uneven surface (but that's a reserved word since processing-3.0) 4 | 5 | require 'pbox2d' 6 | require_relative 'lib/surface' 7 | 8 | attr_reader :terrain, :box2d, :particles 9 | 10 | def setup 11 | sketch_title 'Bumpy Surface Noise' 12 | # Initialize box2d physics and create the world 13 | @box2d = Box2D.new(self) 14 | box2d.init_options(gravity: [0, -20]) 15 | box2d.create_world 16 | # to later set a custom gravity 17 | # box2d.gravity([0, -20]) 18 | # Create the empty list 19 | @particles = [] 20 | # Create the terrain 21 | @terrain = Surface.new(box2d) 22 | end 23 | 24 | def draw 25 | # If the mouse is pressed, we make new particles 26 | # We must always step through time! 27 | background(138, 66, 54) 28 | # Draw the terrain 29 | terrain.display 30 | # NB ? reqd to call mouse_pressed value, else method gets called. 31 | particles << Particle.new(box2d, mouse_x, mouse_y, rand(2.0..6)) if mouse_pressed? 32 | # Draw all particles 33 | particles.each(&:display) 34 | # Particles that leave the screen, we delete them 35 | # (note they have to be deleted from both the box2d world and our list 36 | particles.reject!(&:done) 37 | # Just drawing the framerate to see how many particles it can handle 38 | fill(0) 39 | text(format('framerate: %s', frame_rate.to_i), 12, 16) 40 | end 41 | 42 | def settings 43 | size(500, 300) 44 | smooth 4 45 | end 46 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/collision_listening.rb: -------------------------------------------------------------------------------- 1 | require 'pbox2d' 2 | require_relative 'lib/custom_listener' 3 | require_relative 'lib/particle' 4 | require_relative 'lib/boundary' 5 | 6 | attr_reader :box2d, :particles, :wall 7 | 8 | def setup 9 | sketch_title 'Collision Listening' 10 | @box2d = Box2D.new(self) 11 | box2d.create_world 12 | box2d.add_listener(CustomListener.new) 13 | @particles = [] 14 | @wall = Boundary.new(box2d, width / 2, height - 5, width, 10) 15 | end 16 | 17 | def draw 18 | background(255) 19 | if rand < 0.1 20 | particles << Particle.new( 21 | world: box2d, 22 | location: Vec2D.new(rand(width), 20), 23 | radius: rand(4..8) 24 | ) 25 | end 26 | particles.each { |p| p.display(app: self) } 27 | particles.reject!(&:done) 28 | wall.display(self) 29 | end 30 | 31 | def settings 32 | size 400, 400 33 | end 34 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/data/java_args.txt: -------------------------------------------------------------------------------- 1 | -XX:CompileCommand=dontinline,org.jruby.runtime.invokedynamic.InvokeDynamicSupport::invocationFallback 2 | 3 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/distance_joint/boundary.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # A fixed boundary class 6 | class Boundary 7 | extend Forwardable 8 | def_delegators(:@app, :box2d, :rect_mode, :rect, :fill, :stroke) 9 | # A boundary is a simple rectangle with x, y, width, and height 10 | attr_reader :x, :y, :w, :h 11 | 12 | def initialize(x, y, w, h) 13 | @x = x 14 | @y = y 15 | @w = w 16 | @h = h 17 | @app = Processing.app 18 | # Define the polygon 19 | sd = PolygonShape.new 20 | # Figure out the box2d coordinates 21 | box2dW = box2d.scale_to_world(w / 2) 22 | box2dH = box2d.scale_to_world(h / 2) 23 | # We're just a box 24 | sd.setAsBox(box2dW, box2dH) 25 | # Create the body 26 | bd = BodyDef.new 27 | bd.type = BodyType::STATIC 28 | bd.position.set(box2d.processing_to_world(x, y)) 29 | b = box2d.createBody(bd) 30 | # Attached the shape to the body using a Fixture 31 | b.createFixture(sd, 1) 32 | end 33 | 34 | # Draw the boundary, if it were at an angle we'd have to do something fancier 35 | def display 36 | fill(0) 37 | stroke(0) 38 | rect_mode(Java::ProcessingCore::PConstants::CENTER) 39 | rect(x, y, w, h) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/distance_joint/distance_joint.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Example demonstrating distance joints 6 | # A bridge is formed by connected a series of particles with joints 7 | 8 | require 'pbox2d' 9 | require 'forwardable' 10 | require_relative 'boundary' 11 | require_relative 'pair' 12 | require_relative 'particle' 13 | require_relative 'particle_system' 14 | 15 | attr_reader :box2d, :boundaries, :system 16 | 17 | def setup 18 | sketch_title 'Distance Joint' 19 | # Initialize box2d physics and create the world 20 | @box2d = Box2D.new(self) 21 | box2d.create_world 22 | @system = ParticleSystem.new 23 | @boundaries = [] 24 | # Add a bunch of fixed boundaries 25 | boundaries << Boundary.new(width / 4, height - 5, width / 2 - 50, 10) 26 | boundaries << Boundary.new(3 * width / 4, height - 50, width / 2 - 50, 10) 27 | end 28 | 29 | def draw 30 | background(255) 31 | system.run 32 | # Display all the boundaries 33 | boundaries.each(&:display) 34 | fill(0) 35 | text('Click mouse to add connected particles.', 10, 20) 36 | end 37 | 38 | def mouse_pressed 39 | system.add_pair(mouse_x, mouse_y) 40 | end 41 | 42 | def settings 43 | size(640, 360) 44 | end 45 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/distance_joint/pair.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | require 'forwardable' 5 | 6 | # Series of Particles connected with distance joints 7 | class Pair 8 | extend Forwardable 9 | def_delegators(:@app, :box2d, :stroke, :line, :stroke_weight) 10 | attr_reader :p1, :p2, :len, :joint 11 | 12 | # Chain constructor 13 | def initialize(x, y) 14 | @app = Processing.app 15 | @len = 32 16 | @p1 = Particle.new(x, y) 17 | @p2 = Particle.new(x + rand(-1..1.0), y + rand(-1..1.0)) 18 | djd = DistanceJointDef.new 19 | # Connection between previous particle and this one 20 | djd.bodyA = p1.body 21 | djd.bodyB = p2.body 22 | # Equilibrium length 23 | djd.length = box2d.scale_to_world(len) 24 | # These properties affect how springy the joint is 25 | djd.frequencyHz = 3 # Try a value less than 5 (0 for no elasticity) 26 | djd.dampingRatio = 0.1 # Ranges between 0 and 1 (1 for no springiness) 27 | # Make the joint. 28 | @joint = box2d.world.create_joint(djd) 29 | end 30 | 31 | def kill_bodies 32 | box2d.world.destroy_joint(joint) 33 | @joint = nil 34 | box2d.destroy_body(p1.body) 35 | box2d.destroy_body(p2.body) 36 | end 37 | 38 | # Is the pair ready for deletion? 39 | def done? 40 | # Let's find the screen position of the particle 41 | pos1 = box2d.body_coord(p1.body) 42 | pos2 = box2d.body_coord(p2.body) 43 | # Is it off the screen? 44 | if (0..@app.width).include?(pos1.x) || (0..@app.width).include?(pos2.x) 45 | return false if (0..@app.height).include?(pos1.y) || (0..@app.height).include?(pos2.y) 46 | end 47 | kill_bodies 48 | true 49 | end 50 | 51 | def display 52 | pos1 = box2d.body_coord(p1.body) 53 | pos2 = box2d.body_coord(p2.body) 54 | stroke(0) 55 | stroke_weight(2) 56 | line(pos1.x, pos1.y, pos2.x, pos2.y) 57 | p1.display 58 | p2.display 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/distance_joint/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # A circular particle 6 | class Particle 7 | extend Forwardable 8 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :box2d, :height, :line, 9 | :push_matrix, :pop_matrix, :ellipse, :rotate, :translate) 10 | # We need to keep track of a Body and a radius 11 | attr_reader :body, :r 12 | 13 | def initialize(x, y) 14 | @r = 8 15 | @app = Processing.app 16 | # Define a body 17 | bd = BodyDef.new 18 | # Set its position 19 | bd.position = box2d.processing_to_world(x, y) 20 | bd.type = BodyType::DYNAMIC 21 | @body = box2d.world.createBody(bd) 22 | 23 | # Make the body's shape a circle 24 | cs = CircleShape.new 25 | cs.m_radius = box2d.scale_to_world(r) 26 | 27 | fd = FixtureDef.new 28 | fd.shape = cs 29 | # Parameters that affect physics 30 | fd.density = 1 31 | fd.friction = 0.01 32 | fd.restitution = 0.3 33 | 34 | # Attach fixture to body 35 | body.createFixture(fd) 36 | body.setLinearVelocity(Vec2.new(rand(-5..5), rand(2..5))) 37 | end 38 | 39 | # This function removes the particle from the box2d world 40 | def kill_body 41 | box2d.destroy_body(body) 42 | end 43 | 44 | # Is the particle ready for deletion? 45 | def done 46 | # Let's find the screen position of the particle 47 | pos = box2d.body_coord(body) 48 | # Is it off the bottom of the screen? 49 | if pos.y > height + r * 2 50 | kill_body 51 | return true 52 | end 53 | false 54 | end 55 | 56 | def display 57 | # We look at each body and get its screen position 58 | pos = box2d.body_coord(body) 59 | # Get its angle of rotation 60 | a = body.get_angle 61 | push_matrix 62 | translate(pos.x, pos.y) 63 | rotate(a) 64 | fill(127) 65 | stroke(0) 66 | stroke_weight(2) 67 | ellipse(0, 0, r * 2, r * 2) 68 | # Let's add a line so we can see the rotation 69 | line(0, 0, r, 0) 70 | pop_matrix 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/distance_joint/particle_system.rb: -------------------------------------------------------------------------------- 1 | # run system with a single command 2 | module Runnable 3 | def run 4 | reject!(&:done?) 5 | each(&:display) 6 | end 7 | end 8 | 9 | # A custom enumerable class, it is so easy in ruby 10 | class ParticleSystem 11 | include Runnable 12 | include Enumerable 13 | extend Forwardable 14 | def_delegators(:@pairs, :each, :reject!, :<<) 15 | 16 | def initialize 17 | @pairs = [] 18 | end 19 | 20 | def add_pair(x, y) 21 | self << Pair.new(x, y) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/lib/boundary.rb: -------------------------------------------------------------------------------- 1 | CENTER ||= Java::ProcessingCore::PConstants::CENTER 2 | # The boundary class is used to create a floor in this 3 | # sketch. Note it does not have a change method 4 | class Boundary 5 | attr_reader :box2d, :x, :y, :w, :h, :b 6 | 7 | def initialize(b2d, x, y, w, h) 8 | @box2d = b2d 9 | @x = x 10 | @y = y 11 | @w = w 12 | @h = h 13 | sd = PolygonShape.new 14 | box2d_w = box2d.scale_to_world(w / 2) 15 | box2d_h = box2d.scale_to_world(h / 2) 16 | sd.set_as_box(box2d_w, box2d_h) 17 | # Create the body 18 | bd = BodyDef.new 19 | bd.type = BodyType::STATIC 20 | bd.position.set(box2d.processing_to_world(x, y)) 21 | @b = box2d.create_body(bd) 22 | # Attached the shape to the body using a Fixture 23 | b.create_fixture(sd, 1) 24 | b.set_user_data(self) 25 | end 26 | 27 | # Draw the boundary, if it were at an angle we'd have to do something fancier 28 | def display(app) 29 | app.fill(0) 30 | app.stroke(0) 31 | app.rect_mode(CENTER) 32 | app.rect(x, y, w, h) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/lib/box.rb: -------------------------------------------------------------------------------- 1 | # A Box class, note how to access class ParticleGroupDef in jruby 2 | # which is currently not on an included path for pbox2d 3 | class Box 4 | def initialize(b2d, x, y) 5 | w = rand(1..3) 6 | h = rand(1..3) 7 | shape = PolygonShape.new 8 | pos = b2d.processing_to_world(x, y) 9 | shape.setAsBox(w, h, pos, 0) 10 | pd = Java::OrgJbox2dParticle::ParticleGroupDef.new 11 | pd.shape = shape 12 | b2d.world.create_particle_group(pd) 13 | end 14 | end 15 | 16 | # The boundary class is used to create fixtures 17 | class Boundary 18 | include Processing::Proxy 19 | attr_reader :box2d, :x, :y, :w, :h, :b 20 | 21 | def initialize(b2d, x, y, w, h) 22 | @box2d = b2d 23 | @x = x 24 | @y = y 25 | @w = w 26 | @h = h 27 | sd = PolygonShape.new 28 | box2d_w = box2d.scale_to_world(w / 2) 29 | box2d_h = box2d.scale_to_world(h / 2) 30 | sd.setAsBox(box2d_w, box2d_h) 31 | # Create the body 32 | bd = BodyDef.new 33 | bd.type = BodyType::STATIC 34 | bd.position.set(box2d.processing_to_world(x, y)) 35 | @b = box2d.create_body(bd) 36 | # Attached the shape to the body using a Fixture 37 | b.create_fixture(sd, 1) 38 | end 39 | 40 | # Draw the boundary, if it were at an angle we'd have to do something fancier 41 | def display 42 | fill(0) 43 | stroke(0) 44 | rect_mode(CENTER) 45 | rect(x, y, w, h) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/lib/custom_listener.rb: -------------------------------------------------------------------------------- 1 | # A custom listener allows us to get the physics engine to 2 | # to call our code, on say contact (collisions) 3 | class CustomListener 4 | include ContactListener 5 | 6 | def begin_contact(cp) 7 | # Get both fixtures 8 | f1 = cp.getFixtureA 9 | f2 = cp.getFixtureB 10 | # Get both bodies 11 | b1 = f1.getBody 12 | b2 = f2.getBody 13 | # Get our objects that reference these bodies 14 | o1 = b1.getUserData 15 | o2 = b2.getUserData 16 | return unless [o1, o2].all? { |obj| obj.respond_to?(:change) } 17 | 18 | o1.change 19 | o2.change 20 | end 21 | 22 | def end_contact(_cp); end 23 | 24 | def pre_solve(_cp, _m); end 25 | 26 | def post_solve(_cp, _ci); end 27 | end 28 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/liquid_fun_test.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # Basic example of falling rectangles 5 | 6 | require 'pbox2d' 7 | require_relative 'lib/box' 8 | 9 | attr_reader :boxes, :boundaries, :box2d 10 | 11 | def setup 12 | sketch_title 'Liquid Fun Test' 13 | # @box2d = Box2D.new(self) 14 | # box2d.init_options(gravity: [0, -10]) 15 | # box2d.create_world 16 | @box2d = WorldBuilder.build(app: self, gravity: [0, -10]) 17 | @boundaries = [] 18 | @boxes = [] 19 | box2d.world.set_particle_radius(0.15) 20 | box2d.world.set_particle_damping(0.2) 21 | boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10) 22 | boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10) 23 | end 24 | 25 | def mouse_pressed 26 | boxes << Box.new(box2d, mouse_x, mouse_y) 27 | end 28 | 29 | def draw 30 | background(255) 31 | boundaries.each(&:display) 32 | pos_buffer = box2d.world.particle_position_buffer 33 | return if pos_buffer.nil? 34 | 35 | stroke(0) 36 | stroke_weight(2) 37 | pos_buffer.each do |buf| 38 | pos = box2d.world_to_processing(buf) 39 | point(pos.x, pos.y) 40 | end 41 | fill(0) 42 | text(format('f.p.s %s', frame_rate.to_i), 10, 60) 43 | end 44 | 45 | def settings 46 | size(640, 360, P2D) 47 | end 48 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/liquidy.rb: -------------------------------------------------------------------------------- 1 | require 'pbox2d' 2 | require_relative 'lib/particle_system' 3 | attr_reader :box2d, :boundaries, :systems 4 | 5 | def setup 6 | sketch_title 'Liquidy' 7 | @box2d = Box2D.new(self) 8 | box2d.init_options(gravity: [0, -20]) 9 | box2d.create_world 10 | # to set a custom gravity otherwise 11 | # box2d.gravity([0, -20]) 12 | # Create Arrays 13 | @systems = [] 14 | @boundaries = [] 15 | # Add a bunch of fixed boundaries 16 | boundaries << Boundary.new(box2d, 50, 100, 300, 5, -0.3) 17 | boundaries << Boundary.new(box2d, 250, 175, 300, 5, 0.5) 18 | end 19 | 20 | def draw 21 | background(255) 22 | # Run all the particle systems 23 | if systems.size > 0 24 | systems.each do |system| 25 | system.run 26 | system.add_particles(box2d, rand(0..2)) 27 | end 28 | end 29 | # Display all the boundaries 30 | boundaries.each(&:display) 31 | end 32 | 33 | def mouse_pressed 34 | # Add a new Particle System whenever the mouse is clicked 35 | systems << ParticleSystem.new(box2d, 0, mouse_x, mouse_y) 36 | end 37 | 38 | def settings 39 | size(400, 300) 40 | end 41 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/mouse_joint/boundary.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # A fixed boundary class (now incorporates angle) 6 | class Boundary 7 | extend Forwardable 8 | def_delegators(:@app, :fill, :no_fill, :stroke, :rect, :rect_mode, :box2d, 9 | :stroke_weight, :translate, :push_matrix, :pop_matrix, :rotate) 10 | # A boundary is a simple rectangle with x,y,width,and height 11 | attr_reader :x, :y, :w, :h, :b 12 | 13 | def initialize(x, y, w, h, a) 14 | @x = x 15 | @y = y 16 | @w = w 17 | @h = h 18 | @a = a 19 | @app = Processing.app 20 | # Define the polygon 21 | sd = PolygonShape.new 22 | # Figure out the box2d coordinates 23 | box2dw = box2d.scale_to_world(w / 2) 24 | box2dh = box2d.scale_to_world(h / 2) 25 | # We're just a box 26 | sd.setAsBox(box2dw, box2dh) 27 | # Create the body 28 | bd = BodyDef.new 29 | bd.type = BodyType::STATIC 30 | bd.angle = a 31 | bd.position.set(box2d.processing_to_world(x, y)) 32 | @b = box2d.create_body(bd) 33 | # Attached the shape to the body using a Fixture 34 | b.create_fixture(sd, 1) 35 | end 36 | 37 | # Draw the boundary, if it were at an angle we'd have to do something fancier 38 | def display 39 | no_fill 40 | stroke(127) 41 | fill(127) 42 | stroke_weight(1) 43 | rect_mode(Java::ProcessingCore::PConstants::CENTER) 44 | a = b.get_angle 45 | push_matrix 46 | translate(x, y) 47 | rotate(-a) 48 | rect(0, 0, w, h) 49 | pop_matrix 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/mouse_joint/dummy_spring.rb: -------------------------------------------------------------------------------- 1 | # dummy_spring.rb by Martin Prout 2 | # using duck-typing so class can stand in for Spring 3 | 4 | # Using this class avoids test for nil 5 | class DummySpring 6 | def initialize; end 7 | 8 | # If it exists we set its target to the mouse location 9 | def update(_x, _y); end 10 | 11 | def display; end 12 | 13 | # This is the key function where 14 | # we attach the spring to an x,y location 15 | # and the Box object's location 16 | def bind(x, y, box) 17 | @spring = Spring.new.tap do |spr| 18 | spr.bind(x, y, box) 19 | end 20 | end 21 | 22 | def destroy; end 23 | end 24 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/mouse_joint/mouse_joint.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Basic example of controlling an object with the mouse (by attaching a spring) 6 | require 'pbox2d' 7 | require 'forwardable' 8 | require_relative 'box' 9 | require_relative 'boundary' 10 | require_relative 'spring' 11 | require_relative 'dummy_spring' 12 | # A reference to our box2d world 13 | attr_reader :box2d, :boundaries, :box, :spring 14 | 15 | def setup 16 | sketch_title 'Mouse Joint' 17 | # Initialize box2d physics and create the world 18 | @box2d = Box2D.new self 19 | box2d.create_world 20 | # Make the box 21 | @box = Box.new(width / 2, height / 2) 22 | # Make a dummy spring 23 | @spring = DummySpring.new 24 | # Add a bunch of fixed boundaries 25 | @boundaries = [] 26 | boundaries << Boundary.new(width / 2, height - 5, width, 10, 0) 27 | boundaries << Boundary.new(width / 2, 5, width, 10, 0) 28 | boundaries << Boundary.new(width - 5, height / 2, 10, height, 0) 29 | boundaries << Boundary.new(5, height / 2, 10, height, 0) 30 | end 31 | 32 | # When the mouse is released we're done with the spring 33 | def mouse_released 34 | spring.destroy 35 | @spring = DummySpring.new 36 | end 37 | 38 | # When the mouse is pressed we. . . 39 | def mouse_pressed 40 | # Check to see if the mouse was clicked on the box and if so create 41 | # a real spring and bind the mouse location to the box with a spring 42 | return unless box.contains(mouse_x, mouse_y) 43 | 44 | @spring = spring.bind(mouse_x, mouse_y, box) 45 | end 46 | 47 | def draw 48 | background(255) 49 | # Always alert the spring to the new mouse location 50 | spring.update(mouse_x, mouse_y) 51 | # Draw the boundaries 52 | boundaries.each(&:display) 53 | # Draw the box 54 | box.display 55 | # Draw the spring (it only appears when active) 56 | spring.display 57 | end 58 | 59 | def settings 60 | size(640, 360) 61 | end 62 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/mouse_joint/spring.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Class to describe the spring joint (displayed as a line) 6 | class Spring 7 | extend Forwardable 8 | def_delegators(:@app, :line, :box2d, :stroke, :stroke_weight) 9 | # This is the box2d object we need to create 10 | attr_reader :mouse_joint 11 | 12 | def initialize 13 | @app = Processing.app 14 | end 15 | 16 | # If it exists we set its target to the mouse location 17 | def update(x, y) 18 | # Always convert to world coordinates! 19 | mouse_world = box2d.processing_to_world(x, y) 20 | mouse_joint.set_target(mouse_world) 21 | end 22 | 23 | def display 24 | # We can get the two anchor points 25 | v1 = Vec2.new 26 | mouse_joint.getAnchorA(v1) 27 | v2 = Vec2.new 28 | mouse_joint.getAnchorB(v2) 29 | # Convert them to screen coordinates 30 | vd1 = box2d.world_to_processing(v1) 31 | vd2 = box2d.world_to_processing(v2) 32 | # And just draw a line 33 | stroke(0) 34 | stroke_weight(1) 35 | line(vd1.x, vd1.y, vd2.x, vd2.y) 36 | end 37 | 38 | # This is the key function where 39 | # we attach the spring to an x,y location 40 | # and the Box object's location 41 | def bind(x, y, box) 42 | # Define the joint 43 | md = MouseJointDef.new 44 | # Body A is just a fake ground body for simplicity 45 | # (there isn't anything at the mouse) 46 | md.bodyA = box2d.ground_body 47 | # Body 2 is the box's boxy 48 | md.bodyB = box.body 49 | # Get the mouse location in world coordinates 50 | mp = box2d.processing_to_world(x, y) 51 | # And that's the target 52 | md.target.set(mp) 53 | # Some stuff about how strong and bouncy the spring should be 54 | md.maxForce = 1000.0 * box.body.m_mass 55 | md.frequencyHz = 5.0 56 | md.dampingRatio = 0.9 57 | # Make the joint! 58 | @mouse_joint = box2d.world.create_joint(md) 59 | end 60 | 61 | def destroy 62 | # We can get rid of the joint when the mouse is released 63 | box2d.world.destroy_joint(mouse_joint) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/polygons.rb: -------------------------------------------------------------------------------- 1 | # Basic example of falling rectangles 2 | require 'pbox2d' 3 | require_relative 'lib/custom_shape' 4 | 5 | attr_reader :box2d, :boundaries, :polygons 6 | 7 | def setup 8 | sketch_title 'Polygons' 9 | # Initialize box2d physics and create the world 10 | @box2d = Box2D.new(self) 11 | box2d.init_options(gravity: [0, -20]) 12 | box2d.create_world 13 | # To later set a custom gravity 14 | # box2d.gravity([0, -20] 15 | # Create Arrays 16 | @polygons = [] 17 | @boundaries = [] 18 | # Add a bunch of fixed boundaries 19 | boundaries << Boundary.new(box2d, width / 4, height - 5, width / 2 - 50, 10, 0) 20 | boundaries << Boundary.new(box2d, 3 * width / 4, height - 50, width / 2 - 50, 10, 0) 21 | boundaries << Boundary.new(box2d, width - 5, height / 2, 10, height, 0) 22 | boundaries << Boundary.new(box2d, 5, height / 2, 10, height, 0) 23 | end 24 | 25 | def draw 26 | background(255) 27 | # Display all the boundaries 28 | boundaries.each(&:display) 29 | # Display all the polygons 30 | polygons.each(&:display) 31 | # polygons that leave the screen, we delete them 32 | # (note they have to be deleted from both the box2d world and our list 33 | polygons.reject!(&:done) 34 | end 35 | 36 | def mouse_pressed 37 | polygons << CustomShape.new(box2d, mouse_x, mouse_y) 38 | end 39 | 40 | def settings 41 | size(640, 360) 42 | smooth 43 | end 44 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/revolute_joint/box.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | require 'forwardable' 5 | 6 | # A rectangular box 7 | class Box 8 | extend Forwardable 9 | def_delegators(:@app, :push_matrix, :pop_matrix, :fill, :rotate, :box2d, 10 | :stroke_weight, :translate, :rect_mode, :stroke, :rect) 11 | # We need to keep track of a Body and a width and height 12 | attr_reader :body, :w, :h 13 | 14 | # Constructor 15 | def initialize(x, y, w, h, lock) 16 | @w = w 17 | @h = h 18 | @app = Processing.app 19 | # Define and create the body 20 | bd = BodyDef.new 21 | bd.position.set(box2d.processing_to_world(Vec2.new(x, y))) 22 | bd.type = lock ? BodyType::STATIC : BodyType::DYNAMIC 23 | 24 | @body = box2d.createBody(bd) 25 | 26 | # Define the shape -- a (this is what we use for a rectangle) 27 | sd = PolygonShape.new 28 | box2dW = box2d.scale_to_world(w / 2) 29 | box2dH = box2d.scale_to_world(h / 2) 30 | sd.setAsBox(box2dW, box2dH) 31 | 32 | # Define a fixture 33 | fd = FixtureDef.new 34 | fd.shape = sd 35 | # Parameters that affect physics 36 | fd.density = 1 37 | fd.friction = 0.3 38 | fd.restitution = 0.5 39 | 40 | body.createFixture(fd) 41 | 42 | # Give it some initial random velocity 43 | body.setLinearVelocity(Vec2.new(rand(-5..5), rand(2..5))) 44 | body.setAngularVelocity(rand(-5..5)) 45 | end 46 | 47 | # This function removes the particle from the box2d world 48 | def killBody 49 | box2d.destroyBody(body) 50 | end 51 | 52 | # Drawing the box 53 | def display 54 | # We look at each body and get its screen position 55 | pos = box2d.body_coord(body) 56 | # Get its angle of rotation 57 | a = body.getAngle 58 | 59 | rect_mode(Java::ProcessingCore::PConstants::CENTER) 60 | push_matrix 61 | translate(pos.x, pos.y) 62 | rotate(-a) 63 | fill(127) 64 | stroke(0) 65 | stroke_weight(2) 66 | rect(0, 0, w, h) 67 | pop_matrix 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/revolute_joint/revolute_joint.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Example demonstrating revolute joint 6 | require 'pbox2d' 7 | require 'forwardable' 8 | require_relative 'windmill' 9 | require_relative 'particle' 10 | 11 | attr_reader :box2d, :windmill, :particles 12 | 13 | def setup 14 | sketch_title 'Revolute Joint' 15 | @box2d = Box2D.new(self) 16 | box2d.createWorld 17 | @windmill = Windmill.new(width / 2, 175) 18 | @particles = [] 19 | end 20 | 21 | # Click the mouse to turn on or off the motor 22 | def mouse_pressed 23 | windmill.toggle_motor 24 | end 25 | 26 | def draw 27 | background(255) 28 | if rand < 0.1 29 | sz = rand(4.0..8) 30 | particles << Particle.new(rand(width / 2 - 100..width / 2 + 100), -20, sz) 31 | end 32 | particles.each(&:display) 33 | particles.reject!(&:done?) 34 | # Draw the windmill 35 | windmill.display 36 | status = windmill.motor_on? ? 'ON' : 'OFF' 37 | fill(0) 38 | text(format("Click mouse to toggle motor.\nMotor: %s", status), 10, height - 30) 39 | end 40 | 41 | def settings 42 | size(640, 360) 43 | end 44 | -------------------------------------------------------------------------------- /chp05_physicslibraries/box2d/revolute_joint/windmill.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | require 'forwardable' 5 | require_relative 'box' 6 | 7 | # Class to describe a fixed spinning object 8 | class Windmill 9 | extend Forwardable 10 | def_delegators(:@app, :ellipse, :no_stroke, :box2d, :fill) 11 | # Our object is two boxes and one joint 12 | # Consider making the fixed box much smaller and not drawing it 13 | attr_reader :joint, :box1, :box2 14 | 15 | def initialize(x, y) 16 | @app = Processing.app 17 | # Initialize locations of two boxes 18 | @box1 = Box.new(x, y - 20, 120, 10, false) 19 | @box2 = Box.new(x, y, 10, 40, true) 20 | # Define joint as between two bodies 21 | rjd = RevoluteJointDef.new 22 | # NB: using java_send to access the unreachable 'initialize' method 23 | rjd.java_send :initialize, [Body, Body, Vec2], box1.body, box2.body, box1.body.getWorldCenter 24 | # Turning on a motor (optional) 25 | rjd.motorSpeed = Math::PI * 2 # how fast? 26 | rjd.maxMotorTorque = 1000.0 # how powerful? 27 | rjd.enableMotor = false # is it on? 28 | # There are many other properties you can set for a Revolute joint 29 | # For example, you can limit its angle between a minimum and a maximum 30 | # See box2d manual for more 31 | # Create the joint 32 | @joint = box2d.world.createJoint(rjd) 33 | end 34 | 35 | # Turn the motor on or off 36 | def toggle_motor 37 | joint.enableMotor(!joint.isMotorEnabled) 38 | end 39 | 40 | def motor_on? 41 | joint.isMotorEnabled 42 | end 43 | 44 | def display 45 | box2.display 46 | box1.display 47 | # Draw anchor just for debug 48 | anchor = box2d.vector_to_processing(box1.body.getWorldCenter) 49 | fill(0) 50 | no_stroke 51 | ellipse(anchor.x, anchor.y, 8, 8) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/README.md: -------------------------------------------------------------------------------- 1 | ### Toxiclibs 2 | These examples require installation of the 'toxiclibs' gem. Whilst they work they are mainly a translation exercise and could do with a bit re-factoring for ruby-processing. But are already a bit more elegant than java/vanilla processing. 3 | ```bash 4 | gem install toxiclibs 5 | ``` 6 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/attract_repel/attract_repel.rb: -------------------------------------------------------------------------------- 1 | require 'toxiclibs' 2 | require 'forwardable' 3 | require_relative 'attractor' 4 | require_relative 'particle' 5 | 6 | attr_reader :particles, :attractor, :physics 7 | 8 | def setup 9 | sketch_title 'Attract Repel' 10 | @physics = Physics::VerletPhysics2D.new 11 | physics.setDrag(0.01) 12 | @particles = (0..50).map do 13 | Particle.new( 14 | app: self, 15 | location: TVec2D.new(rand(width), rand(height)) 16 | ) 17 | end 18 | @attractor = Attractor.new( 19 | app: self, 20 | location: TVec2D.new(width / 2, height / 2) 21 | ) 22 | end 23 | 24 | def draw 25 | background(255) 26 | physics.update 27 | attractor.display 28 | particles.each(&:display) 29 | if mouse_pressed? 30 | attractor.lock 31 | attractor.set(mouse_x, mouse_y) 32 | else 33 | attractor.unlock 34 | end 35 | end 36 | 37 | def settings 38 | size 640, 360 39 | end 40 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/attract_repel/attractor.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | class Attractor < Physics::VerletParticle2D 5 | extend Forwardable 6 | def_delegators(:@app, :fill, :ellipse, :physics, :width) 7 | attr_accessor :r 8 | 9 | def initialize(app:, location:) 10 | super(location) 11 | @app = app 12 | @r = 24 13 | physics.add_particle(self) 14 | physics.add_behavior(Physics::AttractionBehavior2D.new(self, width, 0.1)) 15 | end 16 | 17 | def display 18 | fill(0) 19 | ellipse(x, y, r * 2, r * 2) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/attract_repel/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # class Spore extends the class "VerletParticle2D" 6 | class Particle < Physics::VerletParticle2D 7 | extend Forwardable 8 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse, :physics) 9 | attr_reader :r 10 | 11 | def initialize(app:, location:) 12 | super(location) 13 | @app = app 14 | @r = 8 15 | physics.add_particle(self) 16 | physics.add_behavior(Physics::AttractionBehavior2D.new(self, r * 4, -1)) 17 | end 18 | 19 | def display 20 | fill 127 21 | stroke 0 22 | stroke_weight 2 23 | ellipse x, y, r * 2, r * 2 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/force_directed_graph/node.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # Force directed graph 5 | # Heavily based on: http://code.google.com/p/fidgen/ 6 | # Notice how we are using inheritance here! 7 | # We could have just stored a reference to a VerletParticle object 8 | # inside the Node class, but inheritance is a nice alternative 9 | class Node < Physics::VerletParticle2D 10 | extend Forwardable 11 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse) 12 | 13 | def initialize(app:, location:) 14 | super(location) 15 | @app = app 16 | end 17 | 18 | # All we're doing really is adding a display function to a VerletParticle 19 | def display 20 | fill(50, 200, 200, 150) 21 | stroke(50, 200, 200) 22 | stroke_weight(2) 23 | ellipse(x, y, 16, 16) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/simple_cluster/cluster.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # 3 | # Spring 2010 4 | # Toxiclibs example: http://toxiclibs.org/ 5 | 6 | # Force directed graph 7 | # Heavily based on: http://code.google.com/p/fidgen/ 8 | # A cluster is a grouping of Nodes 9 | class Cluster 10 | extend Forwardable 11 | def_delegators(:@app, :physics, :stroke, :stroke_weight, :line) 12 | attr_reader :nodes, :diameter 13 | 14 | # We initialize a Cluster with a number of nodes, a diameter, and centerpoint 15 | def initialize(app:, number:, diameter:, center:) 16 | # Set the diameter 17 | @diameter, = diameter 18 | @app = app 19 | # Create the nodes 20 | @nodes = (0..number).map { Node.new(app: app, location: center.add(TVec2D.randomVector)) } 21 | # Connect all the nodes with a Spring 22 | nodes[0..nodes.size - 2].each_with_index do |ni, i| 23 | nodes[i + 1..nodes.size - 1].each do |nj| 24 | # A Spring needs two particles, a resting length, and a strength 25 | physics.add_spring(Physics::VerletSpring2D.new(ni, nj, diameter, 0.01)) 26 | end 27 | end 28 | end 29 | 30 | def display 31 | # Show all the nodes 32 | nodes.each(&:display) 33 | end 34 | 35 | # Draw all the internal connections 36 | def show_connections 37 | stroke(0, 150) 38 | stroke_weight(2) 39 | nodes[0..nodes.size - 2].each_with_index do |pi, i| 40 | nodes[i + 1..nodes.size - 1].each do |pj| 41 | line(pi.x, pi.y, pj.x, pj.y) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/simple_cluster/node.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # 3 | # Spring 2010 4 | # Toxiclibs example: http://toxiclibs.org/ 5 | 6 | # Force directed graph 7 | # Heavily based on: http://code.google.com/p/fidgen/ 8 | 9 | # Notice how we are using inheritance here! 10 | # We could have just stored a reference to a VerletParticle object 11 | # inside the Node class, but inheritance is a nice alternative 12 | class Node < Physics::VerletParticle2D 13 | extend Forwardable 14 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse) 15 | 16 | def initialize(app:, location:) 17 | super(location) 18 | @app = app 19 | end 20 | 21 | # All we're doing really is adding a :display function to a VerletParticle 22 | def display 23 | fill(0, 150) 24 | stroke(0) 25 | stroke_weight(2) 26 | ellipse(x, y, 16, 16) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/simple_cluster/simple_cluster.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Force directed graph, 6 | # heavily based on: http://code.google.com/p/fidgen/ 7 | require 'forwardable' 8 | require 'toxiclibs' 9 | require_relative 'cluster' 10 | require_relative 'node' 11 | 12 | attr_reader :physics, :cluster, :f, :show_physics, :show_particles 13 | 14 | def setup 15 | sketch_title 'Simple Cluster' 16 | @f = createFont('Georgia', 12, true) 17 | @show_physics = true 18 | @show_particles = true 19 | @show_physics = true 20 | @show_particles = true 21 | # Initialize the physics 22 | @physics = Physics::VerletPhysics2D.new 23 | @physics.setWorldBounds(Toxi::Rect.new(10, 10, width - 20, height - 20)) 24 | 25 | # Spawn a new random graph 26 | @cluster = Cluster.new( 27 | app: self, 28 | number: 8, 29 | diameter: 100, 30 | center: TVec2D.new(width / 2, height / 2) 31 | ) 32 | end 33 | 34 | def draw 35 | # Update the physics world 36 | physics.update 37 | background(255) 38 | # Display all points 39 | cluster.display if show_particles 40 | # If we want to see the physics 41 | cluster.show_connections if show_physics 42 | # Instructions 43 | fill(0) 44 | text_font(f) 45 | text("'p' to display or hide particles\n'c' to display or hide connections\n'n' for new graph", 10, 20) 46 | end 47 | 48 | # Key press commands 49 | def key_pressed 50 | case key 51 | when 'c' 52 | @show_physics = !show_physics 53 | @show_particles = true if show_physics 54 | when 'p' 55 | @show_particles = !show_particles 56 | @show_particles = true unless show_physics 57 | when 'n' 58 | physics.clear 59 | @cluster = Cluster.new( 60 | app: self, 61 | number: rand(3..20), 62 | diameter: rand(10..width / 2), 63 | center: TVec2D.new(width / 2, height / 2) 64 | ) 65 | end 66 | end 67 | 68 | def settings 69 | size(640, 360) 70 | end 71 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/simple_spring/particle.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | 3 | # The Nature of Code 4 | # Daniel Shiffman 5 | # http://natureofcode.com 6 | # Notice how we are using inheritance here! 7 | # We could have just stored a reference to a VerletParticle2D object 8 | # inside the Particle class, but inheritance is an alternative 9 | class Particle < Physics::VerletParticle2D 10 | extend Forwardable 11 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse) 12 | def initialize(loc) 13 | super(loc) 14 | @app = Processing.app 15 | end 16 | 17 | # All we're doing really is adding a display function to a VerletParticle 18 | def display 19 | fill(127) 20 | stroke(0) 21 | stroke_weight(2) 22 | ellipse(x, y, 32, 32) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/simple_spring/simple_spring.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Simple Toxiclibs Spring requires 'toxiclibs gem' 6 | 7 | require 'toxiclibs' 8 | require_relative 'particle' 9 | 10 | attr_reader :physics, :p1, :p2 11 | 12 | def setup 13 | sketch_title 'Simple Spring' 14 | # Initialize the physics 15 | @physics = Physics::VerletPhysics2D.new 16 | physics.addBehavior(Physics::GravityBehavior2D.new(TVec2D.new(0, 0.5))) 17 | # Set the world's bounding box 18 | physics.setWorldBounds(Toxi::Rect.new(0, 0, width, height)) 19 | # Make two particles 20 | @p1 = Particle.new(TVec2D.new(width / 2, 20)) 21 | @p2 = Particle.new(TVec2D.new(width / 2 + 160, 20)) 22 | # Lock one in place 23 | p1.lock 24 | # Make a spring connecting both Particles 25 | spring = Physics::VerletSpring2D.new(p1, p2, 160, 0.01) 26 | # Anything we make, we have to add into the physics world 27 | physics.addParticle(p1) 28 | physics.addParticle(p2) 29 | physics.addSpring(spring) 30 | end 31 | 32 | def draw 33 | # Update the physics world 34 | physics.update 35 | background(255) 36 | # Draw a line between the particles 37 | stroke(0) 38 | stroke_weight(2) 39 | line(p1.x, p1.y, p2.x, p2.y) 40 | # Display the particles 41 | p1.display 42 | p2.display 43 | # Move the second one according to the mouse 44 | return unless mouse_pressed? 45 | 46 | p2.lock 47 | p2.set_x mouse_x 48 | p2.set_y mouse_y 49 | p2.unlock 50 | end 51 | 52 | def settings 53 | size(640, 360) 54 | end 55 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_body/blanket.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | class Blanket 5 | extend Forwardable 6 | def_delegators(:@app, :physics, :width) 7 | attr_reader :particles, :springs 8 | 9 | def initialize(app) 10 | @app = app 11 | @particles = [] 12 | @springs = [] 13 | w = 20 14 | h = 20 15 | len = 10 16 | strength = 0.125 17 | h.times do |y| 18 | w.times do |x| 19 | p = Particle.new(TVec2D.new(width / 2 + x * len - w * len / 2, y * len)) 20 | physics.addParticle(p) 21 | particles << p 22 | if x > 0 23 | previous = particles[particles.size - 2] 24 | c = Connection.new(p, previous, len, strength) 25 | physics.add_spring(c) 26 | springs << c 27 | end 28 | next unless y > 0 29 | 30 | above = particles[particles.size - w - 1] 31 | c = Connection.new(p, above, len, strength) 32 | physics.add_spring(c) 33 | springs << c 34 | end 35 | end 36 | topleft = particles[0] 37 | topleft.lock 38 | topright = particles[w - 1] 39 | topright.lock 40 | end 41 | 42 | def display 43 | springs.each(&:display) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_body/connection.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | class Connection < Physics::VerletSpring2D 5 | extend Forwardable 6 | def_delegators(:@app, :stroke, :line) 7 | 8 | def initialize(p1, p2, len, strength) 9 | super(p1, p2, len, strength) 10 | @app = Processing.app 11 | end 12 | 13 | def display 14 | stroke(0) 15 | line(a.x, a.y, b.x, b.y) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_body/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Notice how we are using inheritance here! 6 | # We could have just stored a reference to a VerletParticle object 7 | # inside the Particle class, but inheritance is a nice alternative 8 | class Particle < Physics::VerletParticle2D 9 | extend Forwardable 10 | def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse) 11 | def initialize(loc) 12 | super(loc) 13 | @app = Processing.app 14 | end 15 | 16 | # All we're doing really is adding a display function to a VerletParticle 17 | def display 18 | fill(175) 19 | stroke(0) 20 | ellipse(x, y, 16, 16) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_body/soft_body_square_adapted.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # 5 | # This example is adapted from Karsten Schmidt's SoftBodySquare example 6 | # 7 | #

Softbody square demo is showing how to create a 2D square mesh out of 8 | # verlet particles and make it stable enough to adef total structural 9 | # deformation by including an inner skeleton.

10 | # 11 | #

Usage: move mouse to drag/deform the square

12 | # 13 | # 14 | # Copyright (c) 2008-2009 Karsten Schmidt 15 | # 16 | # This demo & library is free software you can redistribute it and/or 17 | # modify it under the terms of the GNU Lesser General Public 18 | # License as published by the Free Software Foundation either 19 | # version 2.1 of the License, or (at your option) any later version. 20 | # 21 | # http://creativecommons.org/licenses/LGPL/2.1/ 22 | # 23 | # This library is distributed in the hope that it will be useful, 24 | # but WITHOUT ANY WARRANTY without even the implied warranty of 25 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 26 | # Lesser General Public License for more details. 27 | # 28 | # You should have received a copy of the GNU Lesser General Public 29 | # License along with this library if not, write to the Free Software 30 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 31 | # 32 | require 'forwardable' 33 | require 'toxiclibs' 34 | require_relative 'blanket' 35 | require_relative 'connection' 36 | require_relative 'particle' 37 | 38 | attr_reader :b, :physics 39 | 40 | def setup 41 | sketch_title 'Soft Body Square Adapted' 42 | @physics = Physics::VerletPhysics2D.new 43 | physics.addBehavior(Physics::GravityBehavior2D.new(TVec2D.new(0, 0.1))) 44 | @b = Blanket.new(self) 45 | end 46 | 47 | def draw 48 | background(255) 49 | physics.update 50 | b.display 51 | end 52 | 53 | def settings 54 | size(640, 360) 55 | end 56 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_string/particle.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Notice how we are using inheritance here! 6 | # We could have just stored a reference to a VerletPhysics2D object 7 | # inside the Particle class, but inheritance is a nice alternative 8 | 9 | class Particle < Physics::VerletParticle2D 10 | include Processing::Proxy 11 | attr_accessor :radius # Adding a radius for each particle 12 | 13 | def initialize(x, y) 14 | super(x, y) 15 | @radius = 4 16 | end 17 | 18 | # All we're doing really is adding a display function to a VerletParticle2D 19 | def display 20 | fill(127) 21 | stroke(0) 22 | stroke_weight(2) 23 | ellipse(x, y, radius * 2, radius * 2) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /chp05_physicslibraries/toxiclibs/soft_string/soft_string_pendulum.rb: -------------------------------------------------------------------------------- 1 | #

A soft pendulum (series of connected springs)
2 | # The Nature of Code
3 | # Spring 2010

4 | # Copyright (c) 2010 Daniel Shiffman 5 | # 6 | # This demo & library is free software you can redistribute it and/or 7 | # modify it under the terms of the GNU Lesser General Public 8 | # License as published by the Free Software Foundation either 9 | # version 2.1 of the License, or (at your option) any later version. 10 | # 11 | # http://creativecommons.org/licenses/LGPL/2.1/ 12 | # 13 | # This library is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | # Lesser General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public 19 | # License along with this library if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | 22 | require 'toxiclibs' 23 | require_relative 'particle' 24 | require_relative 'chain' 25 | 26 | attr_reader :physics, :chain 27 | 28 | def setup 29 | sketch_title 'Soft String Pendulum' 30 | # Initialize the physics world 31 | @physics = Physics::VerletPhysics2D.new 32 | physics.addBehavior(Physics::GravityBehavior2D.new(TVec2D.new(0, 0.1))) 33 | physics.setWorldBounds(Toxi::Rect.new(0, 0, width, height)) 34 | # Initialize the chain 35 | @chain = Chain.new(physics, 180, 20, 16, 0.2) 36 | end 37 | 38 | def draw 39 | background(255) 40 | # Update physics 41 | physics.update 42 | # Update chain's tail according to mouse location 43 | chain.update_tail(mouse_x, mouse_y) 44 | # Display chain 45 | chain.display 46 | end 47 | 48 | def mouse_pressed 49 | # Check to see if we're grabbing the chain 50 | chain.contains(mouse_x, mouse_y) 51 | end 52 | 53 | def mouse_released 54 | # Release the chain 55 | chain.release 56 | end 57 | 58 | def settings 59 | size(640, 360) 60 | end 61 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_01_Seek/NOC_6_01_Seek.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_6_01_Seek 3 | class Vehicle 4 | attr_reader :location, :velocity, :acceleration 5 | 6 | def initialize(location:) 7 | @acceleration = Vec2D.new 8 | @velocity = Vec2D.new(0, -2) 9 | @location = location 10 | @r = 6 11 | @maxspeed = 4 12 | @maxforce = 0.1 13 | end 14 | 15 | def apply_force(force:) 16 | @acceleration += force 17 | end 18 | 19 | def update 20 | @velocity += acceleration 21 | @velocity.set_mag(@maxspeed) { velocity.mag > @maxspeed } 22 | @location += velocity 23 | @acceleration *= 0 24 | end 25 | 26 | def seek(target:) 27 | desired = target - location 28 | return if desired.mag < EPSILON 29 | 30 | desired.normalize! 31 | desired *= @maxspeed 32 | steer = desired - velocity 33 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 34 | apply_force(force: steer) 35 | end 36 | 37 | def display 38 | theta = velocity.heading + PI / 2 39 | fill(127) 40 | stroke(0) 41 | stroke_weight(1) 42 | push_matrix 43 | translate(location.x, location.y) 44 | rotate(theta) 45 | begin_shape 46 | vertex(0, -@r * 2) 47 | vertex(-@r, @r * 2) 48 | vertex(@r, @r * 2) 49 | end_shape(CLOSE) 50 | pop_matrix 51 | end 52 | end 53 | 54 | attr_reader :seeker 55 | 56 | def setup 57 | sketch_title 'Seek' 58 | @seeker = Vehicle.new(location: Vec2D.new(width / 2, height / 2)) 59 | end 60 | 61 | def draw 62 | background(255) 63 | mouse = Vec2D.new(mouse_x, mouse_y) 64 | fill(200) 65 | stroke(0) 66 | stroke_weight(2) 67 | ellipse(mouse.x, mouse.y, 48, 48) 68 | # Call the appropriate steering behaviors for our agents 69 | seeker.seek(target: mouse) 70 | seeker.update 71 | seeker.display 72 | end 73 | 74 | def settings 75 | size(640, 360) 76 | end 77 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_01_Seek_trail/NOC_6_01_Seek_trail.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_6_01_Seek_trail 3 | 4 | class Vehicle 5 | attr_reader :location, :velocity, :acceleration, :history 6 | 7 | def initialize(location:) 8 | @acceleration = Vec2D.new 9 | @velocity = Vec2D.new(0, -2) 10 | @location = location 11 | @r = 6 12 | @maxspeed = 4 13 | @maxforce = 0.1 14 | @history = [] 15 | end 16 | 17 | def apply_force(force:) 18 | @acceleration += force 19 | end 20 | 21 | def update 22 | @velocity += acceleration 23 | @velocity.set_mag(@maxspeed) { velocity.mag > @maxspeed } 24 | @location += velocity 25 | @acceleration *= 0 26 | history << location.copy 27 | history.shift if history.size > 100 28 | end 29 | 30 | def seek(target:) 31 | desired = target - location 32 | return if desired.mag < EPSILON 33 | 34 | desired.normalize! 35 | desired *= @maxspeed 36 | steer = desired - velocity 37 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 38 | apply_force(force: steer) 39 | end 40 | 41 | def display 42 | begin_shape 43 | stroke(0) 44 | stroke_weight(1) 45 | no_fill 46 | @history.each { |v| vertex(v.x, v.y) } 47 | end_shape 48 | theta = velocity.heading + PI / 2 49 | fill(127) 50 | stroke(0) 51 | stroke_weight(1) 52 | push_matrix 53 | translate(location.x, location.y) 54 | rotate(theta) 55 | begin_shape 56 | vertex(0, -@r * 2) 57 | vertex(-@r, @r * 2) 58 | vertex(@r, @r * 2) 59 | end_shape(CLOSE) 60 | pop_matrix 61 | end 62 | end 63 | 64 | attr_reader :seeker 65 | 66 | def setup 67 | sketch_title 'Seek Trail' 68 | @seeker = Vehicle.new(location: Vec2D.new(width / 2, height / 2)) 69 | end 70 | 71 | def draw 72 | background(255) 73 | mouse = Vec2D.new(mouse_x, mouse_y) 74 | fill(200) 75 | stroke(0) 76 | stroke_weight(2) 77 | ellipse(mouse.x, mouse.y, 48, 48) 78 | # Call the appropriate steering behaviors for our agents 79 | seeker.seek(target: mouse) 80 | seeker.update 81 | seeker.display 82 | end 83 | 84 | def settings 85 | size(640, 360) 86 | end 87 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_02_Arrive/NOC_6_02_Arrive.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_6_02_Arrive 3 | class Vehicle 4 | attr_reader :location, :velocity, :acceleration 5 | 6 | def initialize(location:) 7 | @acceleration = Vec2D.new 8 | @velocity = Vec2D.new(0, -2) 9 | @location = location 10 | @r = 6 11 | @maxspeed = 4 12 | @maxforce = 0.1 13 | end 14 | 15 | def apply_force(force:) 16 | @acceleration += force 17 | end 18 | 19 | def update 20 | @velocity += acceleration 21 | @velocity.set_mag(@maxspeed) { velocity.mag > @maxspeed } 22 | @location += velocity 23 | @acceleration *= 0 24 | end 25 | 26 | def arrive(target:) 27 | desired = target - location 28 | d = desired.mag 29 | 30 | if d < 100 # scale with arbitrary damping withing 100 pixels 31 | desired.set_mag(map1d(d, (0..100), (0..@maxspeed))) 32 | else 33 | desired.set_mag(@maxspeed) 34 | end 35 | steer = desired - velocity 36 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 37 | apply_force(force: steer) 38 | end 39 | 40 | def display 41 | theta = velocity.heading + PI / 2 42 | fill(127) 43 | stroke(0) 44 | stroke_weight(1) 45 | push_matrix 46 | translate(location.x, location.y) 47 | rotate(theta) 48 | begin_shape 49 | vertex(0, -@r * 2) 50 | vertex(-@r, @r * 2) 51 | vertex(@r, @r * 2) 52 | end_shape(CLOSE) 53 | pop_matrix 54 | end 55 | end 56 | 57 | attr_reader :seeker 58 | 59 | def setup 60 | sketch_title 'Arrive' 61 | @seeker = Vehicle.new(location: Vec2D.new(width / 2, height / 2)) 62 | end 63 | 64 | def draw 65 | background(255) 66 | mouse = Vec2D.new(mouse_x, mouse_y) 67 | fill(200) 68 | stroke(0) 69 | stroke_weight(2) 70 | ellipse(mouse.x, mouse.y, 48, 48) 71 | # Call the appropriate steering behaviors for our agents 72 | seeker.arrive(target: mouse) 73 | seeker.update 74 | seeker.display 75 | end 76 | 77 | def settings 78 | size(640, 360) 79 | end 80 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_07_Separation/NOC_6_07_Separation.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_6_07_Separation 3 | 4 | class Vehicle 5 | attr_reader :acceleration, :location, :velocity 6 | 7 | def initialize(location:) 8 | @location = location 9 | @r = 12 10 | @maxspeed = 3 11 | @maxforce = 0.2 12 | @acceleration = Vec2D.new(0, 0) 13 | @velocity = Vec2D.new(0, 0) 14 | end 15 | 16 | def apply_force(force:) 17 | @acceleration += force 18 | end 19 | 20 | def separate(vehicles) 21 | desired_separation = @r * 2 22 | sum = Vec2D.new 23 | count = 0 24 | vehicles.each do |other| 25 | next if other.equal? self 26 | 27 | d = location.dist(other.location) 28 | next unless (EPSILON..desired_separation).cover? d 29 | 30 | diff = (location - other.location).normalize 31 | diff /= d 32 | sum += diff 33 | count += 1 34 | end 35 | return if count == 0 36 | 37 | sum /= count 38 | sum.normalize! 39 | sum *= @maxspeed 40 | steer = sum + velocity 41 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 42 | apply_force(force: steer) 43 | end 44 | 45 | def update 46 | @velocity += acceleration 47 | @velocity.set_mag(@maxspeed) { velocity.mag > @maxspeed } 48 | @location += velocity 49 | @acceleration *= 0 50 | end 51 | 52 | def display 53 | fill(175) 54 | stroke(0) 55 | push_matrix 56 | translate(location.x, location.y) 57 | ellipse(0, 0, @r, @r) 58 | pop_matrix 59 | end 60 | 61 | def borders(width, height) 62 | location.x = width - @r if location.x < 0 63 | location.y = height - @r if location.y < 0 64 | location.x = @r if location.x > width - @r 65 | location.y = @r if location.y > height - @r 66 | end 67 | end 68 | 69 | def setup 70 | sketch_title 'Separation' 71 | @vehicles = Array.new(100) do 72 | Vehicle.new(location: Vec2D.new(rand(width), rand(height))) 73 | end 74 | end 75 | 76 | def draw 77 | background(255) 78 | @vehicles.each do |v| 79 | v.separate(@vehicles) 80 | v.update 81 | v.borders(width, height) 82 | v.display 83 | end 84 | end 85 | 86 | def mouse_dragged 87 | @vehicles << Vehicle.new(location: Vec2D.new(mouse_x, mouse_y)) 88 | end 89 | 90 | def settings 91 | size(640, 360) 92 | end 93 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_08_SeparationAndSeek/NOC_6_08_SeparationAndSeek.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_6_08_SeparationAndSeek 3 | require_relative 'vehicle' 4 | 5 | attr_reader :vehicles 6 | 7 | def setup 8 | sketch_title 'Separation And Seek' 9 | @vehicles = Array.new(100) do 10 | Vehicle.new(location: Vec2D.new(rand(width), rand(height))) 11 | end 12 | end 13 | 14 | def draw 15 | background(255) 16 | vehicles.each do |v| 17 | v.apply_behaviors(vehicles) 18 | v.update 19 | v.borders(width, height) 20 | v.display 21 | end 22 | end 23 | 24 | def mouse_dragged 25 | vehicles << Vehicle.new(location: Vec2D.new(mouse_x, mouse_y)) 26 | end 27 | 28 | def settings 29 | size(640, 360) 30 | end 31 | -------------------------------------------------------------------------------- /chp06_agents/NOC_6_08_SeparationAndSeek/vehicle.rb: -------------------------------------------------------------------------------- 1 | class Vehicle 2 | include Processing::Proxy 3 | attr_reader :location, :velocity, :acceleration 4 | 5 | def initialize(location:) 6 | @location = location 7 | @r = 12 8 | @maxspeed = 3 9 | @maxforce = 0.2 10 | @acceleration = Vec2D.new 11 | @velocity = Vec2D.new 12 | end 13 | 14 | def apply_force(force:) 15 | @acceleration += force 16 | end 17 | 18 | def apply_behaviors(vehicles) 19 | separate_force = separate(vehicles) 20 | seek_force = seek(Vec2D.new(mouse_x, mouse_y)) 21 | separate_force *= 2 22 | apply_force(force: separate_force) 23 | apply_force(force: seek_force) 24 | end 25 | 26 | def seek(target) 27 | desired = target - location 28 | return desired if desired.mag < EPSILON 29 | 30 | desired.normalize! 31 | desired *= @maxspeed 32 | steer = desired - velocity 33 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 34 | steer 35 | end 36 | 37 | def separate(vehicles) 38 | desired_separation = @r * 2 39 | sum = Vec2D.new 40 | count = 0 41 | vehicles.each do |other| 42 | next if other.equal? self 43 | 44 | d = location.dist(other.location) 45 | next unless (EPSILON..desired_separation).include? d 46 | 47 | diff = (location - other.location).normalize 48 | diff /= d 49 | sum += diff 50 | count += 1 51 | end 52 | return sum if count == 0 53 | 54 | sum /= count 55 | sum.normalize! 56 | sum *= @maxspeed 57 | steer = sum + velocity 58 | steer.set_mag(@maxforce) { steer.mag > @maxforce } 59 | sum 60 | end 61 | 62 | def update 63 | @velocity += acceleration 64 | @velocity.set_mag(@maxspeed) { velocity.mag > @maxspeed } 65 | @location += velocity 66 | @acceleration *= 0 67 | end 68 | 69 | def display 70 | fill(175) 71 | stroke(0) 72 | push_matrix 73 | translate(location.x, location.y) 74 | ellipse(0, 0, @r, @r) 75 | pop_matrix 76 | end 77 | 78 | def borders(width, height) 79 | location.x = width - @r if location.x < 0 80 | location.y = height - @r if location.y < 0 81 | location.x = @r if location.x > width - @r 82 | location.y = @r if location.y > height - @r 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /chp07_CA/NOC_7_01_WolframCA_figures/NOC_7_01_WolframCA_figures.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_7_01_WolframCA_figures 3 | class CA 4 | def initialize(r, width) 5 | @ruleset = r 6 | @scl = 20 7 | @cells = Array.new(width / @scl) 8 | @generation = 0 9 | restart 10 | end 11 | 12 | def restart 13 | @cells = Array.new(@cells.size) { 0 } 14 | @cells[@cells.size / 2] = 1 15 | @generation = 0 16 | end 17 | 18 | def randomize 19 | @ruleset = Array.new(@ruleset.size) { rand(2) } 20 | end 21 | 22 | def generate 23 | nextgen = Array.new(@cells.size) 24 | (1...@cells.size - 1).each do |i| 25 | left = @cells[i - 1] 26 | me = @cells[i] 27 | right = @cells[i + 1] 28 | nextgen[i] = rules(left, me, right) 29 | end 30 | @cells = nextgen 31 | @generation += 1 32 | end 33 | 34 | def rules(a, b, c) 35 | s = a.to_s + b.to_s + c.to_s 36 | @ruleset[s.to_i(2)] 37 | end 38 | 39 | def render 40 | @cells.each_index do |i| 41 | if @cells[i] == 1 42 | fill(0) 43 | else 44 | fill(255) 45 | end 46 | stroke(0) 47 | rect(i * @scl, @generation * @scl, @scl, @scl) 48 | end 49 | end 50 | 51 | def finished(height) 52 | @generation > height / @scl 53 | end 54 | end 55 | 56 | def setup 57 | sketch_title 'Noc 7 01 Wolfram Ca Figures' 58 | background(255) 59 | ruleset = [0, 1, 1, 1, 1, 0, 1, 1] 60 | @ca = CA.new(ruleset, width) 61 | end 62 | 63 | def draw 64 | @ca.render 65 | @ca.generate 66 | save_frame('rule222.png') if @ca.finished(height) 67 | end 68 | 69 | def mouse_pressed 70 | background(255) 71 | @ca.randomize 72 | @ca.restart 73 | end 74 | 75 | def settings 76 | size(1800, 600) 77 | end 78 | -------------------------------------------------------------------------------- /chp07_CA/NOC_7_01_WolframCA_simple/NOC_7_01_WolframCA_simple.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_7_01_WolframCA_simple 3 | class CA 4 | attr_reader :w, :generation 5 | 6 | def initialize(width) 7 | @w = 10 8 | @cells = Array.new(width / @w) { 0 } 9 | @cells[@cells.size / 2] = 1 10 | @ruleset = [0, 1, 0, 1, 1, 0, 1, 0] 11 | @generation = 0 12 | end 13 | 14 | def decimal_rule_to_a(dec) 15 | format('%08b', dec).split('').map(&:to_i) 16 | end 17 | 18 | def generate 19 | nextgen = Array.new(@cells.size) 20 | (1...@cells.size - 1).each do |i| 21 | left = @cells[i - 1] 22 | me = @cells[i] 23 | right = @cells[i + 1] 24 | nextgen[i] = rules(left, me, right) 25 | end 26 | @cells = nextgen 27 | @generation += 1 28 | end 29 | 30 | def rules(a, b, c) 31 | idx = (a.to_s + b.to_s + c.to_s).to_i(2) 32 | @ruleset[7 - idx] 33 | end 34 | 35 | def display 36 | @cells.each_index do |i| 37 | if @cells[i] == 1 38 | fill(0) 39 | else 40 | fill(255) 41 | end 42 | no_stroke 43 | rect(i * w, generation * w, w, w) 44 | end 45 | end 46 | end 47 | 48 | def setup 49 | sketch_title 'Wolfram CA Simple' 50 | background(255) 51 | @ca = CA.new(width) 52 | end 53 | 54 | def draw 55 | @ca.display 56 | @ca.generate if @ca.generation < height / @ca.w 57 | end 58 | 59 | def settings 60 | size(800, 400) 61 | end 62 | -------------------------------------------------------------------------------- /chp07_CA/NOC_7_02_GameOfLifeSimple/NOC_7_02_GameOfLifeSimple.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_7_02_GameOfLifeSimple 3 | class GOL 4 | attr_reader :board, :cols, :rows, :w 5 | 6 | def initialize(width, height) 7 | @w = 8 8 | @rows = height / w 9 | @cols = width / w 10 | init 11 | end 12 | 13 | def init 14 | @board = Array.new(cols) do 15 | Array.new(rows) { rand(2).to_i } 16 | end 17 | end 18 | 19 | def generate 20 | nextgen = Array.new(cols) { Array.new(rows, 0) } 21 | (1...cols - 1).each do |x| 22 | (1...rows - 1).each do |y| 23 | neighbors = 0 24 | (-1..1).each do |i| 25 | (-1..1).each do |j| 26 | neighbors += board[x + i][y + j] if board[x + i][y + j] 27 | end 28 | end 29 | # A little trick to subtract the current cell's state since 30 | # we added it in the loop above 31 | neighbors -= board[x][y] 32 | # rules of life 33 | nextgen[x][y] = if board[x][y] == 1 && neighbors < 2 34 | 0 # Loneliness 35 | elsif board[x][y] == 1 && neighbors > 3 36 | 0 # Overpopulation 37 | elsif board[x][y] == 0 && neighbors == 3 38 | 1 # Reproduction 39 | else 40 | board[x][y] # Stasis 41 | end 42 | end 43 | end 44 | @board = nextgen 45 | end 46 | 47 | def display 48 | (0...cols).each do |i| 49 | (0...rows).each do |j| 50 | if board[i][j] == 0 51 | fill(120) 52 | else 53 | fill(255) 54 | end 55 | stroke(0) 56 | rect(i * w, j * w, w, w) 57 | end 58 | end 59 | end 60 | end 61 | 62 | attr_reader :gol 63 | 64 | def setup 65 | sketch_title 'Game Of Life Simple' 66 | frame_rate(24) 67 | @gol = GOL.new(width, height) 68 | end 69 | 70 | def draw 71 | background(255) 72 | gol.generate 73 | gol.display 74 | end 75 | 76 | # reset board when mouse is pressed 77 | def mouse_pressed 78 | gol.init 79 | end 80 | 81 | def settings 82 | size(400, 400) 83 | end 84 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_01_Recursion/NOC_8_01_Recursion.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_01_Recursion 3 | def setup 4 | sketch_title 'Recursion' 5 | end 6 | 7 | def draw 8 | background(255) 9 | draw_circle(width / 2, height / 2, width) 10 | no_loop 11 | end 12 | 13 | # Very simple function that draws one circle 14 | # and recursively calls itself 15 | def draw_circle(x, y, r) 16 | ellipse(x, y, r, r) 17 | # Exit condition, stop when radius is too small 18 | return unless r > 2 19 | 20 | r *= 0.75 21 | # Call the function inside the function! (recursion!) 22 | draw_circle(x, y, r) 23 | end 24 | 25 | def settings 26 | size(640, 360) 27 | end 28 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_02_Recursion/NOC_8_02_Recursion.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_02_Recursion 3 | def setup 4 | sketch_title 'Recursion' 5 | end 6 | 7 | def draw 8 | background(255) 9 | draw_circle(width / 2, height / 2, 400) 10 | no_loop 11 | end 12 | 13 | # Very simple function that draws one circle 14 | # and recursively calls itself 15 | def draw_circle(x, y, r) 16 | stroke(0) 17 | no_fill 18 | ellipse(x, y, r, r) 19 | # Exit condition, stop when radius is too small 20 | return unless r > 2 21 | 22 | # now we draw 2 circles, 1 on the left, 1 on the right 23 | draw_circle(x + r / 2, y, r / 2) 24 | draw_circle(x - r / 2, y, r / 2) 25 | end 26 | 27 | def settings 28 | size(640, 360) 29 | end 30 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_03_Recursion/NOC_8_03_Recursion.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_03_Recursion 3 | def setup 4 | sketch_title 'Recursion' 5 | end 6 | 7 | def draw 8 | background(255) 9 | draw_circle(width / 2, height / 2, 400) 10 | no_loop 11 | end 12 | 13 | # Very simple function that draws one circle 14 | # and recursively calls itself 15 | def draw_circle(x, y, r) 16 | stroke(0) 17 | no_fill 18 | ellipse(x, y, r, r) 19 | # Exit condition, stop when radius is too small 20 | return unless r > 8 21 | 22 | # now we draw 2 circles, 1 on the left, 1 on the right 23 | draw_circle(x + r / 2, y, r / 2) 24 | draw_circle(x - r / 2, y, r / 2) 25 | draw_circle(x, y + r / 2, r / 2) 26 | draw_circle(x, y - r / 2, r / 2) 27 | end 28 | 29 | def settings 30 | size(640, 360) 31 | end 32 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_04_CantorSet/NOC_8_04_CantorSet.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_04_CantorSet 3 | def setup 4 | sketch_title 'Cantor Set' 5 | background(255) 6 | @h = 30 7 | # Call the recursive function 8 | cantor(35, 0, 730) 9 | end 10 | 11 | def cantor(x, y, len) 12 | # recursive exit condition 13 | return unless len >= 1 14 | 15 | # Draw line (as rectangle to make it easier to see) 16 | no_stroke 17 | fill(0) 18 | rect(x, y, len, @h / 3) 19 | # Go down to next y position 20 | y += @h 21 | # Draw 2 more lines 1/3rd the length (without the middle section) 22 | cantor(x, y, len / 3) 23 | cantor(x + len * 2 / 3, y, len / 3) 24 | end 25 | 26 | def settings 27 | size(800, 200) 28 | end 29 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_04_Tree/NOC_8_04_Tree.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_04_Tree 3 | def setup 4 | sketch_title 'Noc 8 04 Tree' 5 | @theta 6 | end 7 | 8 | def draw 9 | background(255) 10 | # Let's pick an angle 0 to 90 degrees based on the mouse position 11 | @theta = map1d(mouse_x, (0..width), (0..PI / 2)) 12 | 13 | # Start the tree from the bottom of the screen 14 | translate(width / 2, height) 15 | stroke(0) 16 | branch(60) 17 | end 18 | 19 | def branch(len) 20 | # Each branch will be 2/3rds the size of the previous one 21 | sw = map1d(len, (2..120), (1..10)) 22 | stroke_weight(sw) 23 | line(0, 0, 0, -len) 24 | # Move to the end of that line 25 | translate(0, -len) 26 | len *= 0.66 27 | # All recursive functions must have an exit condition!!!! 28 | # Here, ours is when the length of the branch is 2 pixels or less 29 | return unless len > 2 30 | 31 | push_matrix # Save the current state of transformation (i.e. where are we now) 32 | rotate(@theta) # Rotate by theta 33 | branch(len) # Ok, now call myself to draw two new branches!! 34 | pop_matrix # Whenever we get back here, we "pop" in order to restore the previous matrix state 35 | # Repeat the same thing, only branch off to the "left" this time! 36 | push_matrix 37 | rotate(-@theta) 38 | branch(len) 39 | pop_matrix 40 | end 41 | 42 | def settings 43 | size(300, 200) 44 | smooth 4 45 | end 46 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_06_Tree/NOC_8_06_Tree.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_06_Tree 3 | # Recursive Tree 4 | # Renders a simple tree-like structure via recursion 5 | # Branching angle calculated as a function of horizontal mouse location 6 | 7 | def setup 8 | sketch_title 'Noc 8 06 Tree' 9 | end 10 | 11 | def draw 12 | background(255) 13 | # Let's pick an angle 0 to 90 degrees based on the mouse position 14 | @theta = map1d(mouse_x, (0..width), (0..PI / 2)) 15 | # Start the tree from the bottom of the screen 16 | translate(width / 2, height) 17 | stroke(0) 18 | branch(60) 19 | end 20 | 21 | def branch(len) 22 | # Each branch will be 2/3rds the size of the previous one 23 | 24 | # sw = map1d(len, (2..120), (1..10)) 25 | # stroke_weight(sw) 26 | stroke_weight(2) 27 | 28 | line(0, 0, 0, -len) 29 | # Move to the end of that line 30 | translate(0, -len) 31 | 32 | len *= 0.66 33 | # All recursive functions must have an exit condition!!!! 34 | # Here, ours is when the length of the branch is 2 pixels or less 35 | if len > 2 36 | push_matrix # Save the current state of transformation (i.e. where are we now) 37 | rotate(@theta) # Rotate by theta 38 | branch(len) # Ok, now call myself to draw two new branches!! 39 | pop_matrix # Whenever we get back here, we "pop" in order to restore the previous matrix state 40 | 41 | # Repeat the same thing, only branch off to the "left" this time! 42 | push_matrix 43 | rotate(-@theta) 44 | branch(len) 45 | pop_matrix 46 | end 47 | end 48 | 49 | def settings 50 | size(640, 360) 51 | end 52 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_06_Tree_static/NOC_8_06_Tree_static.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_06_Tree_static 3 | # Renders a simple tree-like structure via recursion 4 | # Branching angle calculated as a function of horizontal mouse location 5 | 6 | def setup 7 | sketch_title 'Noc 8 06 Tree Static' 8 | end 9 | 10 | def draw 11 | background(255) 12 | # Start the tree from the bottom of the screen 13 | translate(width / 2, height) 14 | stroke(0) 15 | branch(60) 16 | no_loop 17 | end 18 | 19 | def branch(len) 20 | stroke_weight(2) 21 | line(0, 0, 0, -len) 22 | # Move to the end of that line 23 | translate(0, -len) 24 | len *= 0.66 25 | # All recursive functions must have an exit condition!!!! 26 | # Here, ours is when the length of the branch is 2 pixels or less 27 | return unless len > 2 28 | 29 | push_matrix # Save the current state of transformation (i.e. where are we now) 30 | rotate(PI / 5) # Rotate by theta 31 | branch(len) # Ok, now call myself to draw two new branches!! 32 | pop_matrix # Whenever we get back here, we "pop" in order to restore the previous matrix state 33 | # Repeat the same thing, only branch off to the "left" this time! 34 | push_matrix 35 | rotate(-PI / 5) 36 | branch(len) 37 | pop_matrix 38 | end 39 | 40 | def settings 41 | size(800, 200) 42 | smooth 43 | end 44 | -------------------------------------------------------------------------------- /chp08_fractals/NOC_8_07_TreeStochastic/NOC_8_07_TreeStochastic.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # NOC_8_07_TreeStochastic 3 | # Stochastic Tree 4 | # Renders a simple tree-like structure via recursion 5 | # Angles and number of branches are rand 6 | 7 | def setup 8 | sketch_title 'Noc 8 07 Tree Stochastic' 9 | new_tree 10 | end 11 | 12 | def draw 13 | no_loop 14 | end 15 | 16 | def mouse_pressed 17 | new_tree 18 | redraw 19 | end 20 | 21 | def new_tree 22 | background(255) 23 | fill(0) 24 | text('Click mouse to generate a new tree', 10, height - 10) 25 | stroke(0) 26 | push_matrix 27 | # Start the tree from the bottom of the screen 28 | translate(width / 2, height) 29 | # Start the recursive branching! 30 | branch(80) 31 | pop_matrix 32 | end 33 | 34 | def branch(h) 35 | # thickness of the branch is mapped to its length 36 | sw = map1d(h, (2..120), (1..5)) 37 | stroke_weight(sw) 38 | # Draw the actual branch 39 | line(0, 0, 0, -h) 40 | # Move along to end 41 | translate(0, -h) 42 | # Each branch will be 2/3rds the size of the previous one 43 | h *= 0.66 44 | # All recursive functions must have an exit condition!!!! 45 | # Here, ours is when the length of the branch is 2 pixels or less 46 | return unless h > 2 47 | 48 | # A rand number of branches 49 | n = rand(1..4) 50 | n.times do 51 | # Picking a rand angle 52 | theta = rand(-PI / 2..PI / 2) 53 | push_matrix # Save the current state of transformation (i.e. where are we now) 54 | rotate(theta) # Rotate by theta 55 | branch(h) # Ok, now call myself to branch again 56 | pop_matrix # Whenever we get back here, we "pop" in order to restore the previous matrix state 57 | end 58 | end 59 | 60 | def settings 61 | size(800, 200) 62 | end 63 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_02_SmartRockets_superbasic/README.md: -------------------------------------------------------------------------------- 1 | ## Example of using refined includes in ruby-processing 2 | In dna.rb, we only require access to ruby math methods so we just `include Math` in the DNA class 3 | 4 | In population.rb we want to use the helper method `map1d` so we just `include Processing::HelperMethods` in the Population class 5 | 6 | In rocket.rb, we require similar access to processing methods as available to java inner classes, so here we `include Processing::Proxy` this allows us to access `stroke`, `push_matrix`, `pop_matrix`, `rotate` etc. in the Rocket class 7 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_02_SmartRockets_superbasic/dna.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # DNA is an array of vectors 6 | class DNA 7 | include Math 8 | # The maximum strength of the forces 9 | MAX_FORCE = 0.1 10 | TWO_PI = PI * 2 11 | attr_reader :genes, :lifetime 12 | 13 | # Constructor (makes a DNA of random PVectors) 14 | def create_genes(lftm) 15 | @genes = (0...lftm).map do 16 | angle = rand(TWO_PI) 17 | Vec2D.new(cos(angle), sin(angle)) * rand(0..MAX_FORCE) 18 | end 19 | end 20 | 21 | # Constructor #2, creates the instance based on an existing array 22 | def initialize(lifetime, newgenes = nil) 23 | @lifetime = lifetime 24 | # We could make a copy if necessary 25 | # genes = (PVector []) newgenes.clone(); 26 | @genes = newgenes.nil? ? create_genes(lifetime) : newgenes.clone 27 | end 28 | 29 | # CROSSOVER 30 | # Creates new DNA sequence from two (this & and a partner) 31 | def crossover(partner) 32 | child = Array.new(genes.length, Vec2D.new) 33 | # Pick a midpoint 34 | crossover = rand(genes.length) 35 | # Take "half" from one and "half" from the other 36 | genes.length.times do |i| 37 | child[i] = i > crossover ? genes[i] : partner.genes[i] 38 | end 39 | DNA.new(lifetime, child) 40 | end 41 | 42 | # Based on a mutation probability, picks a new random Vector 43 | def mutate(m) 44 | genes.length.times do |i| 45 | if rand < m 46 | angle = rand(TWO_PI) 47 | genes[i] = Vec2D.new(cos(angle), sin(angle)) * rand(0..MAX_FORCE) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_02_SmartRockets_superbasic/rocket.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Pathfinding w/ Genetic Algorithms 6 | 7 | # Rocket class -- this is just like our Boid / Particle class 8 | # the only difference is that it has DNA & fitness 9 | class Rocket 10 | include Processing::Proxy 11 | # All of our physics stuff 12 | attr_reader :location, :velocity, :acceleration, :r, :dna, :target 13 | attr_reader :gene_counter, :hit_target 14 | 15 | # constructor 16 | def initialize(l, dna, target) 17 | @location = l.dup 18 | @dna = dna 19 | @target = target 20 | @acceleration = Vec2D.new 21 | @velocity = Vec2D.new 22 | @r = 4 23 | @gene_counter = 0 24 | @hit_target = false 25 | end 26 | 27 | # Fitness function 28 | # fitness = one divided by distance squared 29 | def fitness 30 | d = dist(location.x, location.y, target.x, target.y) 31 | (1 / d)**2 32 | end 33 | 34 | # Run in relation to all the obstacles 35 | # If I'm stuck, don't bother updating or checking for intersection 36 | def run 37 | check_target # Check to see if we've reached the target 38 | unless hit_target 39 | apply_force(dna.genes[gene_counter]) 40 | @gene_counter = (gene_counter + 1) % dna.genes.length 41 | update 42 | end 43 | display 44 | end 45 | 46 | # Did I make it to the target? 47 | def check_target 48 | @hit_target = dist(location.x, location.y, target.x, target.y) < 12 49 | end 50 | 51 | def apply_force(f) 52 | @acceleration += f 53 | end 54 | 55 | def update 56 | @velocity += acceleration 57 | @location += velocity 58 | @acceleration *= 0 59 | end 60 | 61 | def display 62 | theta = velocity.heading + PI / 2 63 | fill(200, 100) 64 | stroke(0) 65 | push_matrix 66 | translate(location.x, location.y) 67 | rotate(theta) 68 | # Thrusters 69 | rect_mode(CENTER) 70 | fill(0) 71 | rect(-r / 2.0, r * 2, r / 2.0, r) 72 | rect(r / 2.0, r * 2, r / 2.0, r) 73 | # Rocket body 74 | fill(175) 75 | begin_shape(TRIANGLES) 76 | vertex(0, -r * 2) 77 | vertex(-r, r * 2) 78 | vertex(r, r * 2) 79 | end_shape 80 | pop_matrix 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_02_SmartRockets_superbasic/smart_rocket_basic.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # Smart Rockets w/ Genetic Algorithms 6 | 7 | # Each Rocket's DNA is an array of PVectors 8 | # Each PVector acts as a force for each frame of animation 9 | # Imagine an booster on the end of the rocket that can point in any direction 10 | # and fire at any strength every frame. The Rocket's fitness is a function of 11 | # how close it gets to the target as well as how fast it gets there 12 | # This example is inspired by Jer Thorp's Smart Rockets 13 | # http://www.blprnt.com/smartrockets/ 14 | require_relative 'population' 15 | require_relative 'dna' 16 | require_relative 'rocket' 17 | 18 | attr_reader :lifetime, :population, :life_counter, :target, :mutation_rate 19 | 20 | def setup 21 | sketch_title 'Smart Rocket Basic' 22 | # The number of cycles we will allow a generation to live 23 | @lifetime = height 24 | # Initialize variables 25 | @life_counter = 0 26 | @target = Vec2D.new(width / 2, 24) 27 | # Create a population with a mutation rate, and population max 28 | @mutation_rate = 0.01 29 | @population = Population.new(mutation_rate, 50, width, height, target) 30 | end 31 | 32 | def draw 33 | background(255) 34 | # Draw the start and target locations 35 | fill(0) 36 | ellipse(target.x, target.y, 24, 24) 37 | # If the generation hasn't ended yet 38 | if life_counter < lifetime 39 | population.live 40 | @life_counter += 1 41 | # Otherwise a new generation 42 | else 43 | @life_counter = 0 44 | population.fitness 45 | population.selection 46 | population.reproduction 47 | end 48 | # Display some info 49 | fill(0) 50 | text(format('Generation #: %d', population.generations), 10, 18) 51 | text(format('Cycles left: %d', lifetime - life_counter), 10, 36) 52 | end 53 | 54 | # Move the target if the mouse is pressed 55 | # System will adapt to new target 56 | def mouse_pressed 57 | target.x = mouse_x 58 | target.y = mouse_y 59 | end 60 | 61 | def settings 62 | size(640, 360) 63 | end 64 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_03_SmartRockets/dna.rb: -------------------------------------------------------------------------------- 1 | # We only need to include Math module 2 | class DNA 3 | include Math 4 | TWO_PI = PI * 2 5 | attr_reader :genes 6 | 7 | # Constructor (makes a DNA of rand Vectors) 8 | def initialize(newgenes = nil) 9 | @maxforce = 0.1 10 | @lifetime = 400 11 | @genes = newgenes || Array.new(@lifetime) do 12 | angle = rand(TWO_PI) 13 | gene = Vec2D.new(cos(angle), sin(angle)) 14 | gene *= rand(0...@maxforce) 15 | gene 16 | end 17 | # Let's give each Rocket an extra boost of strength for its first frame 18 | @genes[0].normalize! 19 | end 20 | 21 | # CROSSOVER 22 | # Creates new DNA sequence from two (this & and a partner) 23 | def crossover(partner) 24 | child = Array.new(@genes.size) 25 | # Pick a midpoint 26 | crossover = rand(genes.length).to_i 27 | # Take "half" from one and "half" from the other 28 | @genes.each_with_index do |g, i| 29 | child[i] = if i > crossover 30 | g 31 | else 32 | partner.genes[i] 33 | end 34 | end 35 | DNA.new(child) 36 | end 37 | 38 | # Based on a mutation probability, picks a new rand Vector 39 | def mutate(m) 40 | @genes.length.times do |i| 41 | next unless rand < m 42 | 43 | angle = rand(TWO_PI) 44 | @genes[i] = Vec2D.new(cos(angle), sin(angle)) 45 | @genes[i] *= rand(0...@maxforce) 46 | end 47 | @genes[0].normalize! 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_03_SmartRockets/obstacle.rb: -------------------------------------------------------------------------------- 1 | # A class for an obstacle, just a simple rectangle that is drawn 2 | # and can check if a Rocket touches it 3 | # Also using this class for target location 4 | class Obstacle 5 | include Processing::Proxy 6 | attr_reader :location 7 | 8 | def initialize(x, y, w_, h_) 9 | @location = Vec2D.new(x, y) 10 | @w = w_ 11 | @h = h_ 12 | end 13 | 14 | def display 15 | stroke(0) 16 | fill(175) 17 | stroke_weight(2) 18 | rect_mode(CORNER) 19 | rect(location.x, location.y, @w, @h) 20 | end 21 | 22 | def contains(spot) 23 | ((location.x..location.x + @w).include? spot.x) && 24 | ((location.y..location.y + @h).include? spot.y) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /chp09_ga/NOC_9_05_EvolutionEcosystem/evolution_ecosystem.rb: -------------------------------------------------------------------------------- 1 | # EvolutionEcoSystem 2 | # The Nature of Code 3 | # A World of creatures that eat food 4 | # The more they eat, the longer they survive 5 | # The longer they survive, the more likely they are to reproduce 6 | # The bigger they are, the easier it is to land on food 7 | # The bigger they are, the slower they are to find food 8 | # When the creatures die, food is left behind 9 | 10 | require_relative 'world' 11 | 12 | include Eco 13 | 14 | attr_reader :world 15 | 16 | def setup 17 | sketch_title 'Evolution Ecosystem' 18 | @world = World.new(20, width, height) 19 | end 20 | 21 | def draw 22 | background(255) 23 | world.run 24 | end 25 | 26 | def mouse_pressed 27 | world.born(mouse_x, mouse_y) 28 | end 29 | 30 | def mouse_dragged 31 | world.born(mouse_x, mouse_y) 32 | end 33 | 34 | def settings 35 | size(640, 360) 36 | smooth 4 37 | end 38 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/Exercise_10_05_LayeredNetworkAnimation.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # An animated drawing of a Neural Network 6 | load_libraries :neural_network 7 | 8 | attr_reader :network, :output 9 | 10 | def setup 11 | sketch_title 'Exercise 10 05 Layered Network Animation' 12 | # Create the Network object 13 | @network = Network.new(width / 2, height / 2) 14 | layers = 3 15 | inputs = 2 16 | @output = Neuron.new 250, 0 17 | (0...layers).each do |i| 18 | inputs.times do |j| 19 | x = map1d(i, (0..layers), (-250..300)) 20 | y = map1d(j, (0..inputs - 1), (-75..75)) 21 | n = Neuron.new(x, y) 22 | if i > 0 23 | (0...inputs).each do |k| 24 | prev = network.neurons[network.neurons.size - inputs + k - j] 25 | network.connect(prev, n, rand) 26 | end 27 | end 28 | network.connect(n, output, rand) if i == layers - 1 29 | network.add_neuron(n) 30 | end 31 | end 32 | network.add_neuron(output) 33 | end 34 | 35 | def draw 36 | background 255 37 | # Update and display the Network 38 | network.update 39 | network.display 40 | # Every 30 frames feed in an input 41 | network.feedforward(rand, rand) if (frame_count % 30).zero? 42 | end 43 | 44 | def settings 45 | size 640, 360 46 | end 47 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/LayeredNetworkViz.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | load_library :neural_network 5 | 6 | attr_reader :network 7 | 8 | def setup 9 | sketch_title 'Layered Network Viz' 10 | @network = StaticNetwork.new 4, 3, 1 11 | end 12 | 13 | def draw 14 | background 255 15 | network.display 16 | end 17 | 18 | def settings 19 | size 640, 360 20 | smooth 4 21 | end 22 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/library/neural_network/lib/connection.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # An animated drawing of a Neural Network 6 | class Connection 7 | include Processing::Proxy 8 | # Connection is from A to B 9 | attr_reader :a, :b, :weight, :sending, :sender, :output 10 | 11 | def initialize(from, to, w) 12 | @weight = w 13 | @a = from 14 | @b = to 15 | @sending = false 16 | @sender = Vec2D.new 17 | @output = 0 18 | end 19 | 20 | # The Connection is active 21 | def feedforward(val) 22 | @output = val * weight # Compute output 23 | @sender = a.origin # Start animation at originating neuron 24 | @sending = true # Turn on sending 25 | end 26 | 27 | # Update traveling sender 28 | def update 29 | return unless sending # favour a guard clause in ruby 30 | 31 | # Use a simple interpolation 32 | sender.lerp!(b.location, 0.1) 33 | d = sender.dist(b.location) 34 | # If we've reached the end 35 | return unless d < 1 36 | 37 | # Pass along the output! 38 | b.feedforward(output) 39 | @sending = false 40 | end 41 | 42 | # Draw line and traveling circle 43 | def display 44 | stroke(0) 45 | stroke_weight(1 + weight * 4) 46 | draw_line a.location, b.location 47 | return unless sending 48 | 49 | fill(0) 50 | stroke_weight(1) 51 | ellipse(sender.x, sender.y, 16, 16) 52 | end 53 | 54 | def draw_line(a, b) 55 | line a.x, a.y, b.x, b.y 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/library/neural_network/lib/network.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # The Network has a list of neurons 5 | class Network 6 | include Processing::Proxy 7 | attr_reader :neurons, :connections, :location 8 | 9 | def initialize(x, y) 10 | @location = Vec2D.new(x, y) 11 | @neurons = [] 12 | @connections = [] 13 | end 14 | 15 | # We can add a Neuron 16 | def add_neuron(n) 17 | neurons << n 18 | end 19 | 20 | # We can connection two Neurons 21 | def connect(a, b, weight = 0.5) 22 | c = Connection.new a, b, weight 23 | a.join c 24 | # Also add the Connection here 25 | connections << c 26 | end 27 | 28 | # Sending an input to the first Neuron 29 | # We should do something better to track multiple inputs 30 | def feedforward(input1, input2) 31 | n1 = neurons[0] 32 | n1.feedforward(input1) 33 | n2 = neurons[1] 34 | n2.feedforward(input2) 35 | end 36 | 37 | # Update the animation 38 | def update 39 | connections.each(&:update) 40 | end 41 | 42 | # Draw everything 43 | def display 44 | push_matrix 45 | translate(location.x, location.y) 46 | neurons.each(&:display) 47 | connections.each(&:display) 48 | pop_matrix 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/library/neural_network/lib/neuron.rb: -------------------------------------------------------------------------------- 1 | # Daniel Shiffman 2 | # The Nature of Code 3 | # http://natureofcode.com 4 | # An animated drawing of a Neural Network 5 | class Neuron 6 | include Processing::Proxy 7 | # Neuron has a location 8 | attr_reader :location, :connections, :sum, :r 9 | 10 | def initialize(x, y) 11 | @sum = 0 12 | @r = 32 13 | @location = Vec2D.new(x, y) 14 | @connections = [] 15 | end 16 | 17 | # Add a Connection 18 | def join(c) 19 | connections << c 20 | end 21 | 22 | def origin 23 | location.copy 24 | end 25 | 26 | # Receive an input 27 | def feedforward(input) 28 | # Accumulate it 29 | @sum += input 30 | # Activate it? 31 | return sum unless sum > 1 32 | 33 | fire 34 | @sum = 0 # Reset the sum to 0 if it fires 35 | end 36 | 37 | # The Neuron fires 38 | def fire 39 | @r = 64 # It suddenly is bigger 40 | # We send the output through all connections 41 | connections.each { |c| c.feedforward(sum) } 42 | end 43 | 44 | # Draw it as a circle 45 | def display 46 | stroke(0) 47 | stroke_weight(1) 48 | # Brightness is mapped to sum 49 | b = map1d(sum, (0..1), (255..0)) 50 | fill(b) 51 | ellipse(location.x, location.y, r, r) 52 | # Size shrinks down back to original dimensions 53 | @r = lerp(r, 32, 0.1) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/library/neural_network/lib/static_network.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # The static network 5 | class StaticNetwork 6 | include Processing::Proxy 7 | attr_reader :neurons, :location 8 | 9 | def initialize(layers, inputs, _outputs) 10 | @location = Vec2D.new(width / 2, height / 2) 11 | @neurons = [] 12 | output = Neuron.new(250, 0) 13 | layers.times do |i| 14 | inputs.times do |j| 15 | x = map1d(i, (0..layers), (-200..200)) 16 | y = map1d(j, (0..inputs - 1), (-100..100)) 17 | puts "#{j} #{y}" 18 | n = Neuron.new(x, y) 19 | if i > 0 20 | inputs.times do |k| 21 | prev = neurons[neurons.size - inputs + k - j] 22 | prev.join(n) 23 | end 24 | end 25 | n.join(output) if i == layers - 1 26 | neurons << n 27 | end 28 | end 29 | neurons << output 30 | end 31 | 32 | def display 33 | push_matrix 34 | translate(location.x, location.y) 35 | neurons.each(&:display) 36 | pop_matrix 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /chp10_nn/LayeredNetwork/library/neural_network/neural_network.rb: -------------------------------------------------------------------------------- 1 | # This file provides access to the below listed files via ruby-processing load_library 2 | 3 | require_relative 'lib/connection' 4 | require_relative 'lib/neuron' 5 | require_relative 'lib/network' 6 | require_relative 'lib/static_network' 7 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/NOC_10_03_NetworkViz.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # A static drawing of a Neural Network 6 | load_library :neural_network 7 | 8 | attr_reader :network 9 | 10 | def setup 11 | sketch_title 'Noc 10 03 Network Viz' 12 | # Create the Network object 13 | @network = Network.new(width / 2, height / 2) 14 | # Create a bunch of s 15 | a = Neuron.new(-200, 0) 16 | b = Neuron.new(0, 75) 17 | c = Neuron.new(0, -75) 18 | d = Neuron.new(200, 0) 19 | # Connect them 20 | network.connect(a, b) 21 | network.connect(a, c) 22 | network.connect(b, d) 23 | network.connect(c, d) 24 | # Add them to the Network 25 | network.neurons = [a, b, c, d] 26 | end 27 | 28 | def draw 29 | background(255) 30 | # Draw the Network 31 | network.display 32 | no_loop 33 | end 34 | 35 | def settings 36 | size 640, 360 37 | end 38 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/NOC_10_04_NetworkAnimation.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # An animated drawing of a Neural Network 5 | load_libraries :neural_network 6 | 7 | attr_reader :network 8 | 9 | def setup 10 | sketch_title 'Noc 10 04 Network Animation' 11 | # Create the Network object 12 | @network = Network.new(location: Vec2D.new(width / 2, height / 2)) 13 | # Create a bunch of Neurons 14 | a = Neuron.new(location: Vec2D.new(-275, 0)) 15 | b = Neuron.new(location: Vec2D.new(-150, 0)) 16 | c = Neuron.new(location: Vec2D.new(0, 75)) 17 | d = Neuron.new(location: Vec2D.new(0, -75)) 18 | e = Neuron.new(location: Vec2D.new(150, 0)) 19 | f = Neuron.new(location: Vec2D.new(275, 0)) 20 | # Connect them 21 | network.connect(from: a, to: b, weight: 1.0) 22 | network.connect(from: b, to: c, weight: rand) 23 | network.connect(from: b, to: d, weight: rand) 24 | network.connect(from: c, to: e, weight: rand) 25 | network.connect(from: d, to: e, weight: rand) 26 | network.connect(from: e, to: f, weight: 1.0) 27 | # Add them to the Network 28 | network.neurons = [a, b, c, d, e, f] 29 | end 30 | 31 | def draw 32 | background(255) 33 | # Update and display the Network 34 | network.update 35 | network.display 36 | # Every 30 frames feed in an input 37 | network.feedforward(rand) if frame_count % 30 == 0 38 | end 39 | 40 | def settings 41 | size(640, 360) 42 | end 43 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/library/neural_network/lib/connection.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # An animated drawing of a Neural Network 5 | class Connection 6 | include Processing::Proxy 7 | 8 | attr_reader :neuron_a, :neuron_b, :weight, :sending, :sender, :output 9 | 10 | def initialize(from, to, weight) 11 | @weight = weight 12 | @neuron_a = from 13 | @neuron_b = to 14 | @sending = false 15 | @output = 0 16 | end 17 | 18 | # The Connection is active 19 | def feedforward(val) 20 | @output = val * weight # Compute output 21 | @sender = neuron_a.location.copy # Start animation at Neuron A 22 | @sending = true # Turn on sending 23 | end 24 | 25 | # Update traveling sender 26 | def update 27 | return unless sending 28 | 29 | # Use a simple interpolation 30 | sender.lerp!(neuron_b.location, 0.1) 31 | d = sender.dist(neuron_b.location) 32 | # If we've reached the end 33 | return unless d < 1 34 | 35 | # Pass along the output! 36 | neuron_b.feedforward(output) 37 | @sending = false 38 | end 39 | 40 | # Draw line and traveling circle 41 | def display 42 | stroke(0) 43 | stroke_weight(1 + weight * 4) 44 | line(neuron_a.location.x, neuron_a.location.y, neuron_b.location.x, neuron_b.location.y) 45 | return unless sending 46 | 47 | fill(0) 48 | stroke_weight(1) 49 | ellipse(sender.x, sender.y, 16, 16) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/library/neural_network/lib/network.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # An animated drawing of a Neural Network 5 | class Network 6 | include Processing::Proxy 7 | attr_accessor :neurons # this accessor is used to add neurons 8 | attr_reader :connections, :location 9 | 10 | def initialize(location:) 11 | @location = location 12 | @connections = [] 13 | end 14 | 15 | # We can connection two Neurons 16 | def connect(from:, to:, weight: 0.5) 17 | c = Connection.new(from, to, weight) 18 | from.join(c) 19 | # Also add them to the network 20 | connections << c 21 | end 22 | 23 | # Sending an input to the first Neuron 24 | # We should do something better to track multiple inputs 25 | def feedforward(input) 26 | start = neurons.first 27 | start.feedforward(input) 28 | end 29 | 30 | # Update the animation 31 | def update 32 | connections.each(&:update) 33 | end 34 | 35 | # Draw everything 36 | def display 37 | push_matrix 38 | translate(location.x, location.y) 39 | neurons.each(&:display) 40 | connections.each(&:display) 41 | pop_matrix 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/library/neural_network/lib/neuron.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # An animated drawing of a Neural Network 5 | class Neuron 6 | include Processing::Proxy 7 | ACTIVATION_THRESHOLD = 1.0 8 | 9 | attr_reader :connections, :location, :sum, :r 10 | 11 | def initialize(location:) 12 | @location = location 13 | @connections = [] 14 | @r = 32 15 | @sum = 0 16 | end 17 | 18 | # Add a connection to the neuron object 19 | def join(c) 20 | connections << c 21 | end 22 | 23 | # Receive an input 24 | def feedforward(input) 25 | # Accumulate it 26 | @sum += input 27 | # Activate the neuron when it reaches its threshold 28 | return sum unless sum > ACTIVATION_THRESHOLD 29 | 30 | fire 31 | @sum = 0 # On firing the resting action potential is set to 0 32 | end 33 | 34 | # The Neuron fires 35 | def fire 36 | @r = 64 # display a bigger ellipse to indicate firing 37 | # We forward the signal through all connections 38 | connections.each { |c| c.feedforward(sum) } 39 | end 40 | 41 | # Draw it as a circle 42 | def display 43 | stroke(0) 44 | stroke_weight(1) 45 | # Brightness is mapped to the accumulated action potential 46 | b = map1d(sum, (0..1), (255..0)) 47 | fill(b) 48 | ellipse(location.x, location.y, r, r) 49 | # Size shrinks down back to original dimensions 50 | @r = lerp(r, 32, 0.1) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /chp10_nn/SimpleNetwork/library/neural_network/neural_network.rb: -------------------------------------------------------------------------------- 1 | require_relative 'lib/connection' 2 | require_relative 'lib/neuron' 3 | require_relative 'lib/network' 4 | -------------------------------------------------------------------------------- /chp10_nn/xor/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | library 3 | -------------------------------------------------------------------------------- /chp10_nn/xor/.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.takari.polyglot 5 | polyglot-ruby 6 | 0.4.1 7 | 8 | 9 | -------------------------------------------------------------------------------- /chp10_nn/xor/README.md: -------------------------------------------------------------------------------- 1 | Building the xor Library 2 | =================== 3 | 4 | You need maven to use the pom.rb. 5 | 6 | This is the way the JRubyArt jar gets built!! 7 | 8 | But you can start process with mri ruby, so cd this directory and rake to compile and run. 9 | 10 | -------------------------------------------------------------------------------- /chp10_nn/xor/Rakefile: -------------------------------------------------------------------------------- 1 | desc 'Default' 2 | task default: %i[compile install run] 3 | 4 | desc 'Compile' 5 | task :compile do 6 | sh 'mvn package' 7 | end 8 | 9 | desc 'Move xor jar' 10 | task :install do 11 | sh 'mkdir -p library/xor' unless File.exist?('./library/xor') 12 | sh 'mv target/xor-1.0-SNAPSHOT.jar library/xor/xor.jar' 13 | end 14 | 15 | desc 'Run' 16 | task :run do 17 | exec 'k9 -r xor.rb' 18 | end 19 | -------------------------------------------------------------------------------- /chp10_nn/xor/landscape.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | 5 | # "Landscape" example 6 | class Landscape 7 | include Processing::Proxy 8 | 9 | attr_reader :scl, :w, :h, :rows, :cols, :z, :zoff 10 | 11 | def initialize(scl, w, h) 12 | @scl = scl 13 | @w = w 14 | @h = h 15 | @cols = w / scl 16 | @rows = h / scl 17 | @z = Array.new(cols, Array.new(rows, 0.0)) 18 | @zoff = 0 19 | end 20 | 21 | # Calculate height values (based off a neural network) 22 | def calculate(nn) 23 | val = lambda do |curr, net, x, y| 24 | curr * 0.95 + 0.05 * (net.feed_forward([x, y]) * 280.0 - 140.0) 25 | end 26 | @z = (0...cols).map do |i| 27 | (0...rows).map do |j| 28 | val.call(z[i][j], nn, i * 1.0 / cols, j * 1.0 / cols) 29 | end 30 | end 31 | end 32 | 33 | # Render landscape as grid of quads 34 | def render 35 | # Every cell is an individual quad 36 | # (could use quad_strip here, but produces funny results, investigate this) 37 | (0...z.size - 1).each do |x| 38 | (0...z[0].size - 1).each do |y| 39 | # one quad at a time 40 | # each quad's color is determined by the height value at each vertex 41 | # (clean this part up) 42 | no_stroke 43 | push_matrix 44 | begin_shape(QUADS) 45 | translate(x * scl - w * 0.5, y * scl - h * 0.5, 0) 46 | fill(z[x][y] + 127, 220) 47 | vertex(0, 0, z[x][y]) 48 | fill(z[x + 1][y] + 127, 220) 49 | vertex(scl, 0, z[x + 1][y]) 50 | fill(z[x + 1][y + 1] + 127, 220) 51 | vertex(scl, scl, z[x + 1][y + 1]) 52 | fill(z[x][y + 1] + 127, 220) 53 | vertex(0, scl, z[x][y + 1]) 54 | end_shape 55 | pop_matrix 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /chp10_nn/xor/pom.rb: -------------------------------------------------------------------------------- 1 | project 'xor' do 2 | model_version '4.0.0' 3 | id 'nn:xor:1.0-SNAPSHOT' 4 | packaging 'jar' 5 | 6 | description 'Neural Net Library for xor' 7 | 8 | developer 'shiffman' do 9 | name 'Dan Shiffman' 10 | roles 'developer' 11 | end 12 | 13 | properties('maven.compiler.release' => '11', 14 | 'polyglot.dump.pom' => 'pom.xml') 15 | 16 | overrides do 17 | plugin(:compiler, '3.8.1', 18 | 'encoding' => 'UTF-8') 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /chp10_nn/xor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 11 | 4.0.0 12 | nn 13 | xor 14 | 1.0-SNAPSHOT 15 | xor 16 | Neural Net Library for xor 17 | 18 | 19 | shiffman 20 | Dan Shiffman 21 | 22 | developer 23 | 24 | 25 | 26 | 27 | 11 28 | pom.xml 29 | 30 | 31 | 32 | 33 | 34 | maven-compiler-plugin 35 | 3.8.1 36 | 37 | UTF-8 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /chp10_nn/xor/src/main/java/nn/Connection.java: -------------------------------------------------------------------------------- 1 | // Daniel Shiffman 2 | // The Nature of Code, Fall 2006 3 | // Neural Network 4 | 5 | // Class to describe a connection between two neurons 6 | 7 | package nn; 8 | 9 | public class Connection { 10 | 11 | private final Neuron from; // Connection goes from. . . 12 | private final Neuron to; // To. . . 13 | private double weight; // Weight of the connection. . . 14 | 15 | // Constructor builds a connection with a random weight 16 | public Connection(Neuron a_, Neuron b_) { 17 | from = a_; 18 | to = b_; 19 | weight = Math.random()*2-1; 20 | } 21 | 22 | // In case I want to set the weights manually, using this for testing 23 | public Connection(Neuron a_, Neuron b_, double w) { 24 | from = a_; 25 | to = b_; 26 | weight = w; 27 | } 28 | 29 | public Neuron from() { 30 | return from; 31 | } 32 | 33 | public Neuron to() { 34 | return to; 35 | } 36 | 37 | public double weight() { 38 | return weight; 39 | } 40 | 41 | // Changing the weight of the connection 42 | public void adjustWeight(double deltaWeight) { 43 | weight += deltaWeight; 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /chp10_nn/xor/src/main/java/nn/HiddenNeuron.java: -------------------------------------------------------------------------------- 1 | //Daniel Shiffman 2 | //The Nature of Code, Fall 2006 3 | //Neural Network 4 | 5 | // Hidden Neuron Class 6 | // So far not necessary to differentiate these 7 | 8 | package nn; 9 | 10 | public class HiddenNeuron extends Neuron { 11 | 12 | public HiddenNeuron() { 13 | super(); 14 | } 15 | 16 | public HiddenNeuron(int i) { 17 | super(i); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /chp10_nn/xor/src/main/java/nn/InputNeuron.java: -------------------------------------------------------------------------------- 1 | //Daniel Shiffman 2 | //The Nature of Code, Fall 2006 3 | //Neural Network 4 | 5 | // Input Neuron Class 6 | // Has additional functionality to receive beginning input 7 | 8 | package nn; 9 | 10 | public class InputNeuron extends Neuron { 11 | public InputNeuron() { 12 | super(); 13 | } 14 | 15 | public InputNeuron(int i) { 16 | super(i); 17 | } 18 | 19 | public void input(double d) { 20 | output = d; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /chp10_nn/xor/src/main/java/nn/OutputNeuron.java: -------------------------------------------------------------------------------- 1 | package nn; 2 | 3 | public class OutputNeuron extends Neuron { 4 | public OutputNeuron() { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chp10_nn/xor/xor.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # http://natureofcode.com 4 | # XOR Multi-Layered Neural Network Example 5 | # Neural network code is all in the 'code' folder 6 | load_library :xor 7 | 8 | require_relative './landscape' 9 | include_package 'nn' 10 | 11 | ITERATIONS_PER_FRAME = 5 12 | 13 | attr_reader :inputs, :nn, :count, :land, :theta, :f, :result, :known 14 | 15 | def setup 16 | sketch_title 'XOR' 17 | @theta = 0.0 18 | # Create a landscape object 19 | @land = Landscape.new(20, 300, 300) 20 | @f = create_font('Courier', 12, true) 21 | @nn = Network.new(2, 4) 22 | @count = 0 23 | # Create a list of 4 training inputs 24 | @inputs = [] 25 | inputs << [1.0, 0] 26 | inputs << [0, 1.0] 27 | inputs << [1.0, 1.0] 28 | inputs << [0, 0.0] 29 | end 30 | 31 | def draw 32 | lights 33 | ITERATIONS_PER_FRAME.times do 34 | inp = inputs.sample 35 | # Compute XOR 36 | @known = (inp[0] > 0.0 && inp[1] > 0.0) || (inp[0] < 1.0 && inp[1] < 1.0) ? 0 : 1.0 37 | # Train that sucker! 38 | @result = nn.train(inp, known) 39 | @count += 1 40 | end 41 | # Ok, visualize the solution space 42 | background(175) 43 | push_matrix 44 | translate(width / 2, height / 2 + 20, -160) 45 | rotate_x(Math::PI / 3) 46 | rotate_z(theta) 47 | # Put a little BOX on screen 48 | push_matrix 49 | stroke(50) 50 | no_fill 51 | translate(-10, -10, 0) 52 | box(280) 53 | land.calculate(nn) 54 | land.render 55 | # Draw the landscape 56 | pop_matrix 57 | @theta += 0.0025 58 | pop_matrix 59 | # Display overal neural net stats 60 | network_status 61 | end 62 | 63 | def network_status 64 | mse = 0.0 65 | text_font(f) 66 | fill(0) 67 | text('Your friendly neighborhood neural network solving XOR.', 10, 20) 68 | text(format('Total iterations: %d', count), 10, 40) 69 | mse += (result - known) * (result - known) 70 | rmse = Math.sqrt(mse / 4.0) 71 | out = format('Root mean squared error: %.5f', rmse) 72 | hint DISABLE_DEPTH_SORT 73 | text(out, 10, 60) 74 | hint ENABLE_DEPTH_SORT 75 | end 76 | 77 | def settings 78 | size(400, 400, P3D) 79 | end 80 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/README.md: -------------------------------------------------------------------------------- 1 | Alternative xor (neural net) Library 2 | =================== 3 | 4 | Here we translate the java library to pure ruby 5 | 6 | 7 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/landscape.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # https://natureofcode.com 4 | 5 | # "Landscape" example 6 | class Landscape 7 | include Processing::Proxy 8 | 9 | attr_reader :scl, :w, :h, :rows, :cols, :z, :zoff 10 | 11 | def initialize(scl, w, h) 12 | @scl = scl 13 | @w = w 14 | @h = h 15 | @cols = w / scl 16 | @rows = h / scl 17 | @z = (0..cols).map do |_row| 18 | (0..rows).map { 0.0 } 19 | end 20 | @zoff = 0 21 | end 22 | 23 | # Calculate height values (based off a neural network) 24 | def calculate(nn) 25 | val = lambda do |curr, net, x, y| 26 | curr * 0.95 + 0.05 * (net.feed_forward([x, y]) * 280.0 - 140.0) 27 | end 28 | @z = (0...cols).map do |i| 29 | (0...rows).map do |j| 30 | val.call(z[i][j], nn, i * 1.0 / cols, j * 1.0 / cols) 31 | end 32 | end 33 | end 34 | 35 | # Render landscape as grid of quads 36 | def render 37 | # Every cell is an individual quad 38 | # using the propane grid convenience function instead of a nested loop 39 | grid(z.size - 1, z[0].size - 1) do |x, y| 40 | # one quad at a time 41 | # each quad's color is determined by the height value at each vertex 42 | # (clean this part up) 43 | no_stroke 44 | push_matrix 45 | begin_shape(PConstant::QUADS) 46 | translate(x * scl - w * 0.5, y * scl - h * 0.5, 0) 47 | fill(z[x][y] + 127, 220) 48 | vertex(0, 0, z[x][y]) 49 | fill(z[x + 1][y] + 127, 220) 50 | vertex(scl, 0, z[x + 1][y]) 51 | fill(z[x + 1][y + 1] + 127, 220) 52 | vertex(scl, scl, z[x + 1][y + 1]) 53 | fill(z[x][y + 1] + 127, 220) 54 | vertex(0, scl, z[x][y + 1]) 55 | end_shape 56 | pop_matrix 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/lib/connection.rb: -------------------------------------------------------------------------------- 1 | class Connection 2 | attr_reader :from, :to, :weight 3 | 4 | def initialize(from, to, weight = rand(-1..1.0)) 5 | @from = from 6 | @to = to 7 | @weight = weight 8 | end 9 | 10 | def adjust_weight(delta) 11 | @weight += delta 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/lib/input_neuron.rb: -------------------------------------------------------------------------------- 1 | class InputNeuron < Neuron 2 | attr_reader :output 3 | 4 | def input(data) 5 | @output = data 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/lib/neuron.rb: -------------------------------------------------------------------------------- 1 | # Generic Neuron Class 2 | # Can be a bias neuron (true or false) 3 | class Neuron 4 | attr_reader :output, :connections, :bias 5 | 6 | def initialize(output = 0) 7 | @connections = [] 8 | @output = output 9 | @bias = !output.zero? 10 | end 11 | 12 | def add_connection(c) 13 | connections << c 14 | end 15 | 16 | def calc_output 17 | return if bias # do nothing 18 | 19 | sigmoid = ->(x) { 1.0 / (1.0 + Math.exp(-x)) } 20 | sum = 0 21 | bias_value = 0 22 | # fstring = 'Looking through %d connections' 23 | # puts(format(fstring, connections.size)) 24 | connections.each do |c| 25 | from = c.from 26 | to = c.to 27 | # Is this connection moving forward to us 28 | # Ignore connections that we send our output to 29 | if to == self 30 | # This isn't really necessary 31 | # Ttreating the bias individually in case needed to at some point 32 | if from.bias 33 | bias_value = from.output * c.weight 34 | else 35 | sum += from.output * c.weight 36 | end 37 | end 38 | end 39 | # Output is result of sigmoid function 40 | @output = sigmoid.call(bias_value + sum) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/test/connection_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper' 2 | require_relative '../xor' 3 | 4 | class ConnectionTest < Minitest::Test 5 | def test_new_from_to 6 | from = Neuron.new(1) 7 | to = Neuron.new(2) 8 | connection = Connection.new from, to 9 | end 10 | 11 | def test_new_from_to_weight 12 | from = Neuron.new(1) 13 | to = Neuron.new(2) 14 | connection = Connection.new from, to, rand(-1..1.0) 15 | end 16 | 17 | def test_adjust_weight 18 | from = Neuron.new(1) 19 | to = Neuron.new(2) 20 | connection = Connection.new from, to, 0.5 21 | connection.adjust_weight(0.2) 22 | assert_in_epsilon(0.7, connection.weight) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/test/input_neuron_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper' 2 | require_relative '../xor' 3 | 4 | class NeuronTest < Minitest::Test 5 | def test_new_no_param 6 | neuron = InputNeuron.new 7 | assert !neuron.bias 8 | assert neuron.output.zero? 9 | end 10 | 11 | def test_newparam 12 | input = 1 13 | neuron = InputNeuron.new input 14 | assert neuron.bias 15 | assert_equal neuron.output, input 16 | end 17 | 18 | def test_input 19 | neuron = InputNeuron.new 20 | neuron.input 2 21 | assert_equal 2, neuron.output 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/test/network_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper' 2 | require_relative '../xor' 3 | 4 | class NetworkTest < Minitest::Test 5 | def test_new 6 | network = Network.new(10, 10) 7 | assert_equal 11, network.hidden.length 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/test/neuron_test.rb: -------------------------------------------------------------------------------- 1 | require_relative 'test_helper' 2 | require_relative '../xor' 3 | 4 | class NeuronTest < Minitest::Test 5 | attr_reader :neuron, :neuron_one, :neuron_two 6 | 7 | def setup 8 | @neuron = Neuron.new 9 | @neuron_one = Neuron.new 1 10 | @neuron_two = Neuron.new 2 11 | end 12 | 13 | def test_new_no_param 14 | assert !neuron.bias 15 | assert neuron.output.zero? 16 | end 17 | 18 | def test_newparam 19 | input = 1 20 | assert neuron_one.bias 21 | assert_equal neuron_one.output, input 22 | end 23 | 24 | def test_add_connection 25 | to = neuron_one 26 | from = neuron_two 27 | to.add_connection(Connection.new(from, to)) 28 | assert_equal to.connections.length, 1 29 | end 30 | 31 | def test_calc_output 32 | puts neuron.output 33 | puts neuron_one.output 34 | puts neuron_two.output 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | gem 'minitest' 2 | require 'minitest/pride' 3 | require 'minitest/autorun' 4 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/library/xor/xor.rb: -------------------------------------------------------------------------------- 1 | require_relative './lib/neuron' 2 | require_relative './lib/connection' 3 | require_relative './lib/input_neuron' 4 | require_relative './lib/network' 5 | -------------------------------------------------------------------------------- /chp10_nn/xor_ruby/xor.rb: -------------------------------------------------------------------------------- 1 | # The Nature of Code 2 | # Daniel Shiffman 3 | # https://natureofcode.com 4 | # XOR Multi-Layered Neural Network Example 5 | # Neural network code is all in the 'code' folder 6 | 7 | load_library :xor 8 | require_relative 'landscape' 9 | 10 | ITERATIONS_PER_FRAME = 5 11 | 12 | attr_reader :inputs, :nn, :count, :land, :theta, :font, :result, :known 13 | 14 | def setup 15 | sketch_title 'XOR' 16 | @theta = 0.0 17 | # Create a landscape object 18 | @land = Landscape.new(20, 300, 300) 19 | @font = create_font('Courier', 12, true) 20 | @nn = Network.new(2, 4) 21 | @count = 0 22 | # Create a list of 4 training inputs 23 | @inputs = [ 24 | [1.0, 0], 25 | [0, 1.0], 26 | [1.0, 1.0], 27 | [0, 0.0] 28 | ] 29 | end 30 | 31 | def draw 32 | lights 33 | iterate 34 | # Ok, visualize the solution space 35 | background(175) 36 | push_matrix 37 | translate(width / 2, height / 2 + 20, -160) 38 | rotate_x(PI / 3) 39 | rotate_z(theta) 40 | # Put a little BOX on screen 41 | push_matrix 42 | stroke(50) 43 | no_fill 44 | translate(-10, -10, 0) 45 | box(280) 46 | land.calculate(nn) 47 | land.render 48 | # Draw the landscape 49 | pop_matrix 50 | @theta += 0.0025 51 | pop_matrix 52 | # Display overal neural net stats 53 | network_status 54 | end 55 | 56 | def iterate 57 | ITERATIONS_PER_FRAME.times do 58 | inp = inputs.sample 59 | # Compute XOR 60 | first = inp[0] 61 | second = inp[1] 62 | @known = (first > 0.0 && second > 0.0) || (first < 1.0 && second < 1.0) ? 0 : 1.0 63 | # Train that sucker! 64 | @result = nn.train(inp, known) 65 | @count += 1 66 | end 67 | end 68 | 69 | def network_status 70 | mse = 0.0 71 | text_font(font) 72 | fill(0) 73 | text('Your friendly neighborhood neural network solving XOR.', 10, 20) 74 | text(format('Total iterations: %d', count), 10, 40) 75 | mse += (result - known) * (result - known) 76 | rmse = Math.sqrt(mse / 4.0) 77 | out = format('Root mean squared error: %.5f', rmse) 78 | hint DISABLE_DEPTH_SORT 79 | text(out, 10, 60) 80 | hint ENABLE_DEPTH_SORT 81 | end 82 | 83 | def settings 84 | size(400, 400, P3D) 85 | end 86 | --------------------------------------------------------------------------------