Excel 365 Save or Open an ODC File

In this article, I will show you how to open an ODC file in Excel, or create one if you need to.

What is an ODC (Office Data Connection) File?

An ODC file contains the information related to an external data query created with Power Query (also known as Get & Transform) in Excel. The ODC file can be used separately from the workbook file to share the query without sharing the rest of the data in a workbook. This way, queries can be shared across different workbooks where you may want to use the query. For instance, you may have a query to get the latest bitcoin prices. By creating (or using) an ODC file, you can set this query up once and use it in one workbook to make price forecasts and another to monitor the value of your personal portfolio.

Continue reading “Excel 365 Save or Open an ODC File”

Three.js: Particles with a Custom Particle System

In this article, we are going to create a custom particle system that emits and destroys textured particles in Three.js. Like the particles examples on the Three.js website, our particles will be drawn using GL_POINTS. However, we will be using a custom BufferGeometry and ShaderMaterial to get the job done.

Step 1: Template to Start

As you will notice in the video tutorial above, I decided to mimic a Minecraft torch for the example use case of the particle system. This tutorial assumes some basic Three.js knowledge, so I’m providing a template that includes the torch, textures, and code for rendering the torch and orbit controls. You can download the package, including the code, below. If you just want the code, it’s available below the download link. Be aware the code contains references to textures that will be missing, so you will need to replace the textures or replace the names with those of your own.

Download here from Google Drive.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>three.js webgl - particles - sprites</title>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
    <script type="module">
      import * as THREE from "../three.module.js";
      import { OrbitControls } from "./OrbitControls.js";

      let camera,
        scene,
        renderer,
        controls;


      init();
      animate();

      function init() {
        camera = new THREE.PerspectiveCamera(
          75,
          window.innerWidth / window.innerHeight,
          1,
          2000
        );
        camera.position.z = 10;

        scene = new THREE.Scene();
        scene.fog = new THREE.FogExp2(0x000000, 0.005);

        // Create our minecraft torch
        const loader = new THREE.TextureLoader();
        loader.setPath("textures/");
        const side = new THREE.MeshBasicMaterial({
          map: loader.load("side.png"),
        });
        const top = new THREE.MeshBasicMaterial({
          map: loader.load("top.png"),
        });
        const bottom = new THREE.MeshBasicMaterial({
          map: loader.load("bottom.png"),
        });

        const textures = [side, side, top, bottom, side, side];

        const torchGeometry = new THREE.BoxGeometry(0.5, 2.5, 0.5);
        const torch = new THREE.Mesh(torchGeometry, textures);

        scene.add(torch);

        renderer = new THREE.WebGLRenderer();
        renderer.setClearColor(new THREE.Color(0xffffff));
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        controls = new OrbitControls(camera, renderer.domElement);

        //

        window.addEventListener("resize", onWindowResize);
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();

        renderer.setSize(window.innerWidth, window.innerHeight);
      }
      //

      function animate() {
        requestAnimationFrame(animate);

        render();
      }

      function render() {

        renderer.render(scene, camera);
      }
    </script>
  </body>
</html>

Step 2: ParticleSystem Class

In this second step, let’s add in the ParticleSystem base class and shaders. This is the base class that will be used by any ParticleSystem. It takes care of spawning, destroying, and rendering particles. It doesn’t have any default animation, so particles will just sit there until they die. To include various types of animation, we can create sub-classes.

      const vertexShader = `
        uniform float pointMultiplier;
        attribute float scale;
        attribute float alpha;

        varying float alphaToFrag;

        void main() {
          vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
          gl_Position = projectionMatrix * mvPosition;
          gl_PointSize = pointMultiplier * 1500.0 * scale / gl_Position.w;

          alphaToFrag = alpha;
        }
      `;

      const fragmentShader = `
        uniform sampler2D diffuseTexture;

        varying float alphaToFrag;

        void main() {
          gl_FragColor = texture2D(diffuseTexture, gl_PointCoord) * vec4(1.0, 1.0, 1.0, alphaToFrag);
        }
      `;

      class ParticleSystem {
        constructor (texture, emit_every, particle_life) {
          this.texture = texture;
          this.emit_every = emit_every;
          this.particle_life = particle_life;
          this.last_emission = 0;

          this.geometry = new THREE.BufferGeometry();
          this.particles = [];
          this.material = new THREE.ShaderMaterial({
            uniforms: {
              diffuseTexture: { value: texture },
              pointMultiplier: { value: window.innerHeight / window.innerWidth }
            },
            vertexShader,
            fragmentShader,
            blending: THREE.NormalBlending,
            depthTest: true,
            depthWrite: false,
            transparent: true,
            vertexColors: true,
          });

          this.mesh = new THREE.Points(this.geometry, this.material);
          this.clock = new THREE.Clock();
        }

        setPosition(position) {
          this.mesh.position.x = position.x;
          this.mesh.position.y = position.y;
          this.mesh.position.z = position.z;
        }

        getMesh() {
          return this.mesh;
        }

        updateAspect() {
          this.material.uniforms.pointMultiplier.value = window.innerHeight / window.innerWidth;
        }

        spawn() {
          this.particles.push({
            position: [0, 0, 0],
            scale: 1,
            alpha: 1,
            spawnTime: this.clock.elapsedTime,
          });

          this.last_emission = this.clock.elapsedTime;
        }

        update() {
          const elapsedTime = this.clock.getElapsedTime();

          this.particles = this.particles.filter((particle) => elapsedTime - particle.spawnTime < this.particle_life);

          if (elapsedTime - this.last_emission >= this.emit_every) {
            this.spawn();
          }

          this.geometry.setAttribute("position", new THREE.Float32BufferAttribute(this.particles.map((particle) => particle.position).flat(), 3));
          this.geometry.setAttribute("scale", new THREE.Float32BufferAttribute(this.particles.map((particle) => particle.scale).flat(), 1));
          this.geometry.setAttribute("alpha", new THREE.Float32BufferAttribute(this.particles.map((particle) => particle.alpha).flat(), 1));
          this.geometry.attributes.position.needsUpdate = true;
          this.geometry.attributes.scale.needsUpdate = true;
        }
      }

Step 3: Animated Particle Classes

We’ll create two types of particle systems to get our Minecraft torch going. One for the smoke, which will rise up and fade. The second one for the flames, which will gradually get bigger before disappearing. You could create any animation you want in your sub-classes, or expand the base class to create a greater variety of possible appearances and animations.

      class GrowParticleSystem extends ParticleSystem {
        update() {
          for (let i = 0; i < this.particles.length; i++) {
            this.particles[i].position[1] += 0.001;
            this.particles[i].scale += 0.001;
          }

          super.update();
        }
      }

      class SmokeParticleSystem extends ParticleSystem {
        spawn() {
          super.spawn();
          this.particles[this.particles.length - 1].dartX = Math.random() * 0.005 * (Math.random() > 0.5 ? 1 : -1 );
          this.particles[this.particles.length - 1].dartZ = Math.random() * 0.005 * (Math.random() > 0.5 ? 1 : -1 );
        }

        update() {
          for (let i = 0; i < this.particles.length; i++) {
            this.particles[i].position[0] += this.particles[i].dartX;
            this.particles[i].position[1] += 0.005;
            this.particles[i].position[2] += this.particles[i].dartZ;
            this.particles[i].scale -= 0.001;
            this.particles[i].alpha -= 0.01;
          }

          super.update();
        }
      }

Step 4: Adding and Updating the Particle Systems

...
      function init() {
        ...

        const flame = loader.load("flame.png");
        flame.flipY = false;

        const smoke = loader.load("smoke.png");
        smoke.flipY = false;

        flameParticles = new GrowParticleSystem(flame, 1, 2.5);
        flameParticles.setPosition(new THREE.Vector3(0, 2.5 / 2 + 0.25, 0));

        smokeParticles = new SmokeParticleSystem(smoke, 2, 2.5);
        smokeParticles.setPosition(new THREE.Vector3(0, 2.5 / 2 + 0.5, 0));
        scene.add(flameParticles.getMesh());
        scene.add(smokeParticles.getMesh());
        ...

      }

      function onWindowResize() {
        ...

        flameParticles.updateAspect();
        smokeParticles.updateAspect();

        ...
      }

      function animate() {
        ...
        // Whether your function is called render, update, animate, or something else, make sure these get called each frame.
        flameParticles.update();
        smokeParticles.update();

        ...
      }

...

Conclusion

That’s it! You should now have a working particle system. There’s lots of areas to expand this system with a large variety of features, but it’s a solid start that should help you get where you’re going.