Перейти ко вступительной информации

Видео+код: #2/15 THREEJS подписи к точкам на карте планеты

Статья создана: Видео+код: #2/15 THREEJS подписи к точкам на карте планеты

Видео: 17 THREEJS координаты земли

урок 17 по Three JS / урок 15 по планете

Файлы из урока 15 по 3D планете

2-15-prod-planet.zip

GitHUB

JS Код из видео (!на threejs-webpack-starter!)

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

import {BufferGeometryUtils} from 'three/examples/jsm/utils/BufferGeometryUtils'
import {TWEEN} from 'three/examples/jsm/libs/tween.module.min'
// JFT [Just for test]
import Stats from 'three/examples/jsm/libs/stats.module'
// \ JFT
import { TTFLoader } from 'three/examples/jsm/loaders/TTFLoader'
import { TextGeometry } from 'three/src/geometries/TextGeometry'
import { FontLoader } from 'three/src/loaders/FontLoader'

import anime from 'animejs/lib/anime'
//import * as dat from 'dat.gui'

//const gui = new dat.GUI()
const dc=document;
const cns=dc.querySelector('.webgl');
if(!cns)throw 'no canvas.webgl';
const sizes = {width: parseInt(window.getComputedStyle(cns).width),  height: parseInt(window.getComputedStyle(cns).height)}


let o;
const scene = new THREE.Scene();
//scene.background = new THREE.Color('blue');
const camera = new THREE.PerspectiveCamera(45, sizes.width / sizes.height, 0.01, 50);
camera.position.set(0, 10, 5);
const renderer = new THREE.WebGLRenderer({canvas:cns,antialias: true,alpha: true});
renderer.setSize(sizes.width , sizes.height);

renderer.setClearColor(0x000000, 0);

const controls = new OrbitControls(camera, renderer.domElement);
//controls.enablePan = true;

/* const light = new THREE.DirectionalLight(0xffffff,1)
scene.add(light) */

const group=new THREE.Group();// empty groupe for add all rotation object

const params = {
  colors: {
    gradInner: '#c7c7c7', // HEX Color | Inner gradient of "boom"
    gradOuter: '#464646', // HEX Color | Outer gradient of "boom"
    lineColor: '#cd390b', // HEX Color | Default color lines
  },
  mapPoints:{
    base: '#ffffff',// HEX Color | Base map color | FOR COLOR USE #FFFFFF -> AND ANY HEX COLOR VALUE
    sizeOfPoints:0.5,// !FLOAT ONLY! | MIN: 0.1 , MAX: 0.4
    opacityOfOceanPoints:0.1,// !FLOAT ONLY! | ex. 0.1 | MIN: 0.1 - black, MAX: 0.9
    countOfPoints:25000,// INT ONLY | ex. 1000 - 40000 | The more — the more points on the planet, but the more difficult the calculations
    showBackMap:true, // BOOLEAN | Removes the view from the planet map that is in the background | IMPORTANT!  if(showGlowedSphere===true)showBackMap=false
    showSphereToHideBackSide:false, // BOOLEAN | IF TRUE, showBackMap = false || Shows an additional sphere, as if under the map of the planet. This sphere hides the background of the map.
    hiddenShpereColor:'#000000',// HEX Color | If you want to disable showing the background of the planet map, then an additional object is created in the form of a sphere, which also hides some elements on the back of the planet, which is, as it were, in the background from you
    hiddenSphereOpacity:1,
    showGlowedSphere:true,
    showGlowedSphereColor:0x005caf,
  },
  font:'/fonts/Cuprum-VariableFont_wght.ttf',
  reset: ()=>controls.reset()
}
// An array for forming lines, "boom", as well as sticks (highlighting the point where the line arrives)
//The point of arrival of the line and the point from where it flies is made up of two array elements: 0, 1; 2, 3 and so on.
//In the first object of one of the points (the first one is where the line flies from), you can add some user data in order to unify the default settings

//!!! Only two values in it are mandatory: latitude and longitude
const data=[
  // This forms three objects: a line, a "boom", a stick
  { // FROM CHINA
    lat:32.622876, // REQUIRED | Float ex. 42.0 | Earth coordinate latitude
    lon:107.523152, // REQUIRED | Float ex. 42.0 | Earth coordinate longitude
    lineSpeed:2, // Integer | Default 2 | min ≈1, max ≈20 | It's speed - how fast does the animation of the line go from point A to point B
    lineWidth:1,// Float | min ≈.1, max ≈10 | Worked only on Linux system | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line width — https://stackoverflow.com/questions/11638883/thickness-of-lines-using-three-linebasicmaterial
    lineColor:'#ff0000',// HEX Color | Default params.colors.lineColor | Line color in HEX, ex. 0xffffff - it's white
    lineRepeats:100, // Infinity or Integer || 1, 2, 1000, Infinity | Number of line flight repetitions
    boomNeed:true,// Boolean | 'Boom' is added by default | If you do not need "boom", then set the value to false. By default, "boom" passes
    boomSpeed: 4999,// Integer | Default (some random): THREE.Math.randInt(2500, 5000) | min ≈500 , max ≈5000 || THREE.Math.randomInt(2500, 5000)
    boomRadius: 2, // Integer | Default (some random): [5 * THREE.Math.randFloat(.2, .7)] | min ≈.5 , max ≈3 || 5 * THREE.Math.randFloat(.2, .7)
    boomRepeat:Infinity,// Infinity or Integer | Default: Infinity | 1, 2, 1000, Infinity | Number of repeats "boom"
    showStick:true, // Boolean | Default: false | A line from the point where the "boom" arrives
    stickColorTo:'#ffffff',// HEX Color | Default #ffffff | Arrives line color in HEX, ex. 0xffffff - it's white | To create gradient
    stickColorFrom:'#ffffff',// HEX Color | Default #ffffff | Arrives line color in HEX, ex. 0xffffff - it's white | To create gradient
    stickHeight:2, // Integer or Float | Default 1.1 | min ≈1, max ≈5 | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line height
    stickWidth:.02, // Float | Default 0.1 | min ≈.01, max ≈.2 | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line height
    // NEW
    text:`Australia
132`, // String | Max: 50 symbol | ex. 'This is Pekin'
    textColor: '#ff0000', // HEX Color | Default #ffffff
    textSize: .1, // Float | Default: .1 | Depending on the size of the text, an underlay is formed on the background of the text
    textBgColor: '#0086ff', // HEX Color | Default #0086ff | If this parameter is present, then we add a plan from behind
    textStickColor: '#ffffff', // HEX Color | Default #ff00ff
    textDistance: 1.3, // Float | Default 1.1 | min ≈1.1, max ≈2 | Distance from the surface of the planet to the text
    to:{lat:-26.164493,lon:134.742407},// REQUIRED | TO   1 Australia
  },
  // \\ This forms three objects: a line, a "boom", a stick
  {
    lat:7.466688, lon:19.987692,//FROM  2 // Central Africa
    lineSpeed:2,
    lineColor:'#ff0000',
    lineRepeats:Infinity,
    boomNeed:true,
    boomSpeed: 3500,
    boomRadius: 2,
    boomRepeat:100,
    showStick:true,
    stickColorTo:'#00ff00',
    stickColorFrom:'#ffffff',
    stickHeight:1.5,
    stickWidth:.05,
    // NEW
    text:'South America', // String | Max: 50 symbol | ex. 'This is Pekin'
    textColor: '#00cc99', // HEX Color | Default #ffffff
    textSize: .3, // Float | Default: .1 | Depending on the size of the text, an underlay is formed on the background of the text
    textBgColor: '#ffffff', // HEX Color | Default #0086ff
    textStickColor: '#ffffff', // HEX Color | Default #ffffff
    to:{lat:-15.860255, lon:-58.059177},//TO 2 // Central South America
  },
  {
    lat:48.358527, lon:-99.761561,//FROM  3 // South Amer
    lineSpeed:2,
    lineColor:'#00f96f',
    boomNeed:true,
    boomSpeed: 2500,
    boomRadius: 3,
    boomRepeat:Infinity,
    lineRepeats:Infinity,
    showStick:true,
    stickColorTo:'#0000ff',
    stickColorFrom:'#ff0000',
    stickHeight:1,
    stickWidth:.1,
    // NEW
    text:`Greenland
Text1: 0;
Text2 Hello: 1e5`, // String | Max: 50 symbol | ex. 'This is Pekin'
    textColor: '#ff00aa', // HEX Color | Default #ffffff
    textSize: .07, // Float | Default: .1 | Depending on the size of the text, an underlay is formed on the background of the text
    textBgColor: '#ffffff', // HEX Color | Default #0086ff
    textStickColor: '#ffffff', // HEX Color | Default #ffffff
    to:{lat:76.910298, lon:-40.348415},//TO 3 // Greenland
  },
  {
    lat:65.242150, lon:149.801448, // FROM 4 East RU
    lineSpeed:2, // Integer | Default 2 | min ≈1, max ≈20 | It's speed - how fast does the animation of the line go from point A to point B
    lineWidth:1,// Float | min ≈.1, max ≈10 | Worked only on Linux system | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line width — https://stackoverflow.com/questions/11638883/thickness-of-lines-using-three-linebasicmaterial
    lineColor:'#ff0000',// HEX Color | Default params.colors.lineColor | Line color in HEX, ex. 0xffffff - it's white
    lineRepeats:100, // Infinity or Integer || 1, 2, 1000, Infinity | Number of line flight repetitions
    boomNeed:true,// Boolean | 'Boom' is added by default | If you do not need "boom", then set the value to false. By default, "boom" passes
    boomSpeed: 2000,// Integer | Default (some random): THREE.Math.randInt(2500, 5000) | min ≈500 , max ≈5000 || THREE.Math.randomInt(2500, 5000)
    boomRadius: 3, // Integer | Default (some random): [5 * THREE.Math.randFloat(.2, .7)] | min ≈.5 , max ≈3 || 5 * THREE.Math.randFloat(.2, .7)
    boomRepeat:Infinity,// Infinity or Integer | Default: Infinity | 1, 2, 1000, Infinity | Number of repeats "boom"
    showStick:true, // Boolean | Default: false | A line from the point where the "boom" arrives
    stickColorTo:'#ffffff',// HEX Color | Default #ffffff | Arrives line color in HEX, ex. 0xffffff - it's white | To create gradient
    stickColorFrom:'#ffffff',// HEX Color | Default #ffffff | Arrives line color in HEX, ex. 0xffffff - it's white | To create gradient
    stickHeight:2, // Integer or Float | Default 1.1 | min ≈1, max ≈5 | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line height
    stickWidth:.02, // Float | Default 0.1 | min ≈.01, max ≈.2 | ex. for randomization it: THREE.Math.randFloat(.5, 2).toFixed(2) | Arrives line height
    // NEW
    text:`I Love USA | Washington`, // String | Max: 50 symbol | ex. 'This is Pekin'
    textColor: '#ff0000', // HEX Color | Default #ffffff
    textSize: .1, // Float | Default: .1 | Depending on the size of the text, an underlay is formed on the background of the text
    textBgColor: '#f9f9f9', // HEX Color | Default #0086ff | If this parameter is present, then we add a plan from behind
    textStickColor: '#f9f9f9', // HEX Color | Default #ff00ff
    textDistance: 1.05, // Float | Default 1.1 | min ≈1, max ≈2 | Distance from the surface of the planet to the text
    to:{lat:38.870829359139556, lon: -77.05594503672475},//TO 4 // Washington
    //to:{lat:7.952571, lon:-73.546554},//TO 4 // Washington
  },
];

const maxImpactAmount = data.length; // Constant for determining the number "boom"
function isFloat(n){return Number(n) === n && n % 1 !== 0;} // Flote of numbers

// CREATE TEXT AND POINTER
function createText(text='Default text',pos=[0,0,0],rotY=Math.PI,size=.1,font,multiplyScalar=1,color=0xffffff,bgPlane,textStickColor){
  // bgPlane — If this parameter is present, then we add a plan from behind
  const text_=new String(text);
  const textGeo = new TextGeometry(text_,{
    font,  size,  height: 0,  curveSegments:1
  } );
  const textMaterial=new THREE.MeshBasicMaterial({color,side:THREE.BackSide});
  // TEXT OBJ
  text=new THREE.Mesh(textGeo,textMaterial);
  text.position.set(pos[0],pos[1],pos[2]).multiplyScalar(multiplyScalar);
  text.lookAt(new THREE.Vector3())
  text.rotateY(rotY)
  text.translateY(.1)
  text.translateX(.5)
// https://stackoverflow.com/questions/33758313/get-size-of-object3d-in-three-js
  const bbox = new THREE.Box3().setFromObject(text);
  const widthZ=bbox.max.z-bbox.min.z
  anime({targets:text.scale,x:[0,1],y:[0,1],z:[0,1],duration:4000,easing})
  if(bgPlane){
    let factor=2
    const len = (text_.match(/\n/g)||[]).length// 3
    if(len)factor*=len+1
    const x = 0;  const y = 0
    const width = widthZ*2/1.4
    const height = size*factor
    const radius = size*.8
    const shape = new THREE.Shape();// https://stackoverflow.com/questions/65567873/create-a-plane-with-curved-edges-using-planegeometry-three-js
    shape.moveTo( x, y + radius );
    shape.lineTo( x, y + height - radius );
    shape.quadraticCurveTo( x, y + height, x + radius, y + height );
    shape.lineTo( x + width - radius, y + height );
    shape.quadraticCurveTo( x + width, y + height, x + width, y + height - radius );
    shape.lineTo( x + width, y + radius );
    shape.quadraticCurveTo( x + width, y, x + width - radius, y );
    shape.lineTo( x + radius, y );
    shape.quadraticCurveTo( x, y, x, y + radius )
    // PLANE OBJ
    const plane = new THREE.Mesh( new THREE.ShapeGeometry( shape ),  new THREE.MeshBasicMaterial({color:bgPlane || 0x0086ff,side:THREE.DoubleSide}))
    plane.translateY(-height/3.5*factor/2.5)
    plane.translateX(-.05)
    plane.translateZ(-.001)
    text.add(plane)
    if(textStickColor){
      // point for line (necessary temporarily)
      const point = new THREE.Mesh(
        new THREE.CircleGeometry(0,3),
        new THREE.MeshBasicMaterial()
      )
      point.position.set(text.position.x,text.position.y,text.position.z)
      point.rotation.set(text.rotation.x,text.rotation.y,text.rotation.z)
      point.translateY(.1)
      point.translateZ(-.01)
      group.add(point)

      // POINTER OBJ
      const geom = new THREE.BufferGeometry();
      const two=new THREE.Vector3(point.position.x,point.position.y,point.position.z)
      geom.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( [
          text.position.x,text.position.y,text.position.z,
          two.x,two.y,two.z,
          pos[0],pos[1],pos[2],
        ])
        , 3 ) )
      const pointer = new THREE.Mesh(
        geom,  new THREE.MeshBasicMaterial({color:textStickColor || 0x0086ff,side:THREE.DoubleSide, transparent:true})
      );
      group.add(pointer);
      point.remove()
      anime({targets:pointer.material,opacity:[0,1],duration:4000,easing})
    }
  }
  group.add(text);
}

let font
const ttfLoader = new TTFLoader()
const fontLoader = new FontLoader()
ttfLoader.load(
  params.font,
  fnt=>{
      fnt=fontLoader.parse(fnt)
      font=fnt
    }
)
// \ CREATE TEXT AND POINTER

const impacts = []; // Array for "boom"
const trails = []; // Array for animated lines
let isMapLoaded=false // Determines when the planet card is formed
const easing='easeInOutSine'// https://codepen.io/kcjpop/pen/GvdQdX

for(let i=0;i<data.length;i++){ // The cycle that sorting out the values of the object with the data
  if( // We check the availability of strictly necessary data in the array with data
    !data[i].lat
    ||!data[i].lon
    ||!isFloat(data[i].lat)
    ||!isFloat(data[i].lon)
    ||!data[i].to.lat
    ||!data[i].to.lon
    ||!isFloat(data[i].to.lat)
    ||!isFloat(data[i].to.lon)
  ){ // If the check has not passed, then we stop the script
    console.log(data[i],data[i].to);
    throw new Error('Check data lat OR lon!')
  }
  const whereItArrives=cTv(data[i].to); // Constant taking the value where the line flies
  if(!data[i].stickHeight)data[i].stickHeight=1.1; // Setting the default value, in the absence of data from an object with data
  if(data[i].showStick){
    const material = new THREE.ShaderMaterial({//https://discourse.threejs.org/t/draw-a-line-with-a-simple-single-colour-fading-gradient/1775/32
      side:THREE.DoubleSide,
      uniforms: {
        color: {value: new THREE.Color(data[i].stickColorTo || 0xffffff)},
        color2: {value: new THREE.Color(data[i].stickColorFrom ||0xffffff)},
        origin: {value: new THREE.Vector3()},
        limitDistance: {value: parseInt(data[i].stickHeight*5)},
      },
      vertexShader: `
      varying vec2 vUv; // We create a variable, then to convey it to a fragmentShader
      varying vec3 vPos; // We create a variable, then to convey it to a fragmentShader
      void main(){
        vUv=uv;
        vPos = position;
        vec3 pos=position.xyz * sin(1.);
        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
      }`,
      fragmentShader: `
      varying vec2 vUv; // We accept data from vertexShader
      uniform vec3 color; // We accept data from uniforms
      uniform vec3 color2; // We accept data from uniforms
      uniform vec3 origin; // We accept data from uniforms
      uniform float limitDistance; // We accept data from uniforms
      varying vec3 vPos; // We accept data from vertexShader
      void main() {
        vec2 center = vec2((vUv.y - 1.)*1.,(vUv.y - 1.)*1.); // We set a conditional center for one cylinder
        float distance = length(center); // Determine its size
        float opacity = smoothstep(.3,1.,distance); // We make a soft fill
        gl_FragColor = vec4(mix(color2,color, vUv.y), opacity);
      }`,  transparent: true, 
    });
    // Creating and positioning the cylinder - sticks
    const geometry = new THREE.CylinderBufferGeometry(0,data[i].stickWidth || .1,data[i].stickHeight || 1.1);
    const mesh = new THREE.Mesh( geometry, material );
    const stickHeight=data[i].stickHeight*(1/data[i].stickHeight+.085) || 1.05
    mesh.position.set(whereItArrives.x*stickHeight,whereItArrives.y*stickHeight,whereItArrives.z*stickHeight);
    mesh.lookAt(new THREE.Vector3()); // We ask him to look at his normal at the center of the planet
    mesh.rotateX(Math.PI * -.5); // Since it has normal in the middle, before that he was “lying” on the planet, and this code makes him stand perpendicular to it
    mesh.scale.set(0,0,0);
    group.add(mesh)
    let interval=setInterval(()=>{ // Some hack to give the opportunity to load the planet’s map itself, and then show the sticks themselves
      if(isMapLoaded){
        clearInterval(interval);
        interval=undefined
        anime({targets:mesh.scale,x:1,y:1,z:1,easing,duration:2000,}) // We animize the scale
        anime({targets:mesh.position, // We animize the position
        x:[whereItArrives.x*.9,whereItArrives.x*stickHeight],
        y:[whereItArrives.y*.9,whereItArrives.y*stickHeight],
        z:[whereItArrives.z*.9,whereItArrives.z*stickHeight],
        delay:1000,easing,duration:2000,})
      }
    },100);
  }
  const o_=Object.create({ // We create an object for the subsequent creation of "boom" and lines
    prevPosition: cTv(data[i]),
    impactPosition: whereItArrives,
    impactMaxRadius: parseFloat(data[i].boomRadius) || 5 * THREE.Math.randFloat(.2, .7),
    impactRatio: 0,
    trailRatio: {value: 0},
    trailLength: {value: 0},
  })
  impacts.push(o_)
  if(data[i].boomNeed===undefined || data[i].boomNeed!==false){ // If the “boom” is not indicated in the object with data for a particular object, then by default it will be shown
    new TWEEN.Tween({ value:0})
    .to({ value: 1 }, parseInt(data[i].boomSpeed) || THREE.Math.randInt(2500, 5000))
    .onUpdate(val=>{o_.impactRatio = val.value}).start().repeat(data[i].boomRepeat || Infinity)
  }
  // Lines
  makeTrail(i,data[i].lineColor || 0xffffff,data[i].lineWidth || .1); // Creating the line itself. This function also fills the trails array
  const path = trails[i];
  const speed = data[i].lineSpeed || 2;
  const t=new TWEEN.Tween({value: 0}) // We anmile "boom" and lines
  .to({value: 1}, path.geometry.attributes.lineDistance.array[99] / speed * 1000)
  .onUpdate( val => {o_.trailRatio.value = val.value})
  t.start().repeat(data[i].lineRepeats || Infinity)

// ADD FONT AND MORE...
  const forText=Object.create({
    text:data[i].text,
    textColor:data[i].textColor || '#ffffff',
    textSize:data[i].textSize || .1,
    textBgColor:data[i].textBgColor || '#0086ff', // If this parameter is present, then we add a plan from behind
    textStickColor:data[i].textStickColor || '#0086ff',
    textDistance:data[i].textDistance || 1.1,
    textStickColor:data[i].textStickColor || '#0086ff',
  })
  // We are waiting for the font to download over the network
  if(!font&&forText.text){ // Wait TEST IT
    let sti=setInterval(() => {
      if(font){
        clearInterval(sti)
        sti=undefined
        if(forText.text)createText(forText.text,[whereItArrives.x,whereItArrives.y,whereItArrives.z],undefined,forText.textSize,font,forText.textDistance,forText.textColor,forText.textBgColor,forText.textStickColor)
      }
    }, 100);
  }// \ ADD FONT AND MORE...
}
const uniforms = { // For Shader with "boom"
  impacts: {value: impacts},
  maxSize: {value: .04},
  minSize: {value: .03},
  waveHeight: {value: .125},
  scaling: {value: 2},
  gradInner: {value: new THREE.Color(params.colors.gradInner)},
  gradOuter: {value: new THREE.Color(params.colors.gradOuter)}
};
(()=>{ // Creation of the planet map
    const dummyObj = new THREE.Object3D()
    const p = new THREE.Vector3()
    const sph = new THREE.Spherical()
    const geoms = new Array()
    const tex = new THREE.TextureLoader().load('/map.jpg',()=>{
      // https://web.archive.org/web/20120107030109/http://cgafaq.info/wiki/Evenly_distributed_points_on_sphere#Spirals
      const counter = params.mapPoints.countOfPoints
      const rad = 5
      let r = 0
      const dlong = Math.PI * (3 - Math.sqrt(5))
      const dz = 2 / counter
      let long = 0
      let z = 1 - dz / 2
      for(let i = 0; i < counter; i++){
          r = Math.sqrt(1 - z * z);
          p.set( Math.cos(long) * r, z, -Math.sin(long) * r).multiplyScalar(rad);
          z = z - dz;
          long = long + dlong;
          sph.setFromVector3(p);
          dummyObj.lookAt(p);
          dummyObj.updateMatrix();
          const g =  new THREE.PlaneBufferGeometry(2, 2);
          g.applyMatrix4(dummyObj.matrix);
          g.translate(p.x, p.y, p.z);
          const centers = [
            p.x, p.y, p.z,
            p.x, p.y, p.z,
            p.x, p.y, p.z,
            p.x, p.y, p.z
          ];
          const uv = new THREE.Vector2(
            (sph.theta + Math.PI) / (Math.PI * 2),
            1. - sph.phi / Math.PI
          );
          const uvs = [
            uv.x, uv.y,
            uv.x, uv.y,
            uv.x, uv.y,
            uv.x, uv.y
          ];
          g.setAttribute("center", new THREE.Float32BufferAttribute(centers, 3));
          g.setAttribute("bUv", new THREE.Float32BufferAttribute(uvs, 2));
          geoms.push(g);
      }
      const g = BufferGeometryUtils.mergeBufferGeometries(geoms);
      if(params.mapPoints.showSphereToHideBackSide)params.mapPoints.showBackMap=false;
      let sideOfMap=(params.mapPoints.showBackMap)?THREE.DoubleSide:THREE.FrontSide;
      if(!params.mapPoints.showBackMap && params.mapPoints.showSphereToHideBackSide){ // Add sphere hide
        let isTransparent=true;
        if(params.mapPoints.hiddenSphereOpacity===undefined || params.mapPoints.hiddenSphereOpacity === 1)isTransparent=false
        scene.add(
          new THREE.Mesh(
            new THREE.IcosahedronBufferGeometry(rad-.01,16),
            new THREE.MeshBasicMaterial({
              color:params.mapPoints.hiddenShpereColor || '#000000',
              transparent:isTransparent,
              opacity:params.mapPoints.hiddenSphereOpacity || 1,
            })
          )
        )
      }

      // ADD GLOWED SPHERE
      if(params.mapPoints.showGlowedSphere){
        const shaderMaterial_GLOW=new THREE.ShaderMaterial({// Glow
          uniforms:{
                  "c":{type: "f", value:.5},// intencity
                  "p":{type: "f", value:3.},// density
                  "opacity":{type: "f", value:.7},// opacity
                  glowColor: { type: "c", value: new THREE.Color(params.mapPoints.showGlowedSphereColor || 0x0086ff) },//0x00aaff
                  viewVector: { type: "v3", value: new THREE.Vector3(0,0,1) }// glow rotation
              },
              vertexShader:`uniform vec3 viewVector;
      uniform float c;
      uniform float p;
      varying float intensity;
      void main(){
      vec3 vNormal = normalize(normalMatrix * normal);
      vec3 vNormel = normalize(normalMatrix * viewVector);
      intensity = pow(c - dot(vNormal, vNormel), p);
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
      }`,
              fragmentShader:`uniform vec3 glowColor;
      uniform float opacity;
      varying float intensity;
      void main(){
      vec3 glow = glowColor * intensity;
      gl_FragColor = vec4(glow, opacity);
      }`,
              /* side: THREE.FrontSide,*/  transparent: true,
      })
        const glowedSphere=new THREE.Mesh(
          new THREE.IcosahedronGeometry(rad-.003,16),  shaderMaterial_GLOW
        )
        glowedSphere.rotateY(1)
        scene.add(glowedSphere)
      }
      // \ ADD GLOWED SPHERE

      const m = new THREE.MeshBasicMaterial({
        color: new THREE.Color(params.mapPoints.base),
        side: sideOfMap,
        transparent:true,
        onBeforeCompile: shader => {
          shader.uniforms.impacts = uniforms.impacts;
          shader.uniforms.maxSize = uniforms.maxSize;
          shader.uniforms.minSize = uniforms.minSize;
          shader.uniforms.waveHeight = uniforms.waveHeight;
          shader.uniforms.scaling = uniforms.scaling;
          shader.uniforms.gradInner = uniforms.gradInner;
          shader.uniforms.gradOuter = uniforms.gradOuter;
          shader.uniforms.tex = {value: tex};
          shader.vertexShader = `
            struct impact {
              vec3 impactPosition;
              float impactMaxRadius;
              float impactRatio;
            };
            uniform impact impacts[${maxImpactAmount}];
            uniform sampler2D tex;
            uniform float maxSize;
            uniform float minSize;
            uniform float waveHeight;
            uniform float scaling;
            attribute vec3 center;
            attribute vec2 bUv;
            varying float vFinalStep;
            varying float vMap;
            ${shader.vertexShader}
          `.replace(
              `#include <begin_vertex>`,
            `#include <begin_vertex>
            float finalStep = 0.0;
            for (int i = 0; i < ${maxImpactAmount};i++){
              float dist = distance(center, impacts[i].impactPosition);
              float curRadius = impacts[i].impactMaxRadius * impacts[i].impactRatio;
              float sstep = smoothstep(0., curRadius, dist) - smoothstep(curRadius - (.25 * impacts[i].impactRatio ), curRadius, dist);
              sstep *= 1. - impacts[i].impactRatio;
              finalStep += sstep;
            }
            finalStep = clamp(finalStep, 0., 1.);
            vFinalStep = finalStep;
            float map = texture(tex, bUv).g;
            vMap = map;
            float pSize = map < 0.5 ? maxSize : minSize;
            float scale = scaling;
            transformed = (position - center) * pSize * mix(1., scale * 1.25, finalStep) + center; // scale on wave
            transformed += normal * finalStep * waveHeight; // lift on wave
            `
          );
          shader.fragmentShader = `
            uniform vec3 gradInner;
            uniform vec3 gradOuter;
            varying float vFinalStep;
            varying float vMap;
            ${shader.fragmentShader}
            `.replace(
            `vec4 diffuseColor = vec4( diffuse, opacity );`,
            `// shaping the point, pretty much from The Book of Shaders
            vec2 hUv = (vUv - .1);
            int N = 8;
            float a = atan(hUv.x,hUv.y);
            float r = PI2/float(N);
            float d = cos(floor(.5+a/r)*r-a)*length(hUv);
            float f = cos(PI / float(N)) * .5;
            //if (d > f) discard;
            if (length(vUv - ${params.mapPoints.sizeOfPoints}) > ${params.mapPoints.sizeOfPoints}) discard;
            vec3 grad = mix(gradInner, gradOuter, clamp( d / f, 0., 1.)); // gradient
            vec3 diffuseMap = diffuse * ((vMap > .5) ? ${params.mapPoints.opacityOfOceanPoints} : 1.);
            vec3 col = mix(diffuseMap, grad, vFinalStep); // color on wave
            float opct=(vMap > .5)?${params.mapPoints.opacityOfOceanPoints}:1.;
            vec4 diffuseColor = vec4( col , opct );
            `);
        }
      });
      m.defines = {"USE_UV":""};
      o = new THREE.Mesh(g, m);
      trails.forEach(t => group.add(t));
      group.add(o);
      isMapLoaded=!isMapLoaded
    })
  })()

function makeTrail(idx,color,lineWidth){ // Creation of lines
  const pts = new Array(100 * 3).fill(0);
  const g = new THREE.BufferGeometry();
  g.setAttribute("position", new THREE.Float32BufferAttribute(pts, 3));
  const m = new THREE.LineDashedMaterial({
  	color: color || params.colors.lineColor,
    linewidth: lineWidth || 1,
    transparent: true,
  	onBeforeCompile: shader => {
    	shader.uniforms.actionRatio = impacts[idx].trailRatio;
      shader.uniforms.lineLength = impacts[idx].trailLength;
      shader.fragmentShader = `
      	uniform float actionRatio;
        uniform float lineLength;
        ${shader.fragmentShader}
      `.replace(
      	`if ( mod( vLineDistance, totalSize ) > dashSize ) {
		discard;
	}`,
        ` float halfDash = dashSize * .5;
          float currPos = (lineLength + dashSize) * actionRatio;
        	float d = (vLineDistance + halfDash) - currPos;
        	if (abs(d) > halfDash ) discard;
          float grad = ((vLineDistance + halfDash) - (currPos - halfDash)) / halfDash;
        `
      )
      .replace(
      	`vec4 diffuseColor = vec4( diffuse, opacity );`,
        `vec4 diffuseColor = vec4( diffuse, grad );`
      );
    }
  });
  const l = new THREE.Line(g, m)
  l.userData.idx = idx
  if(impacts[idx])setPath(l, impacts[idx].prevPosition, impacts[idx].impactPosition, 1)
  trails.push(l)
}
// based on https://jsfiddle.net/prisoner849/fu59aved/
function setPath(l, startPoint, endPoint, peakHeight, cycles) {
    const pos = l.geometry.attributes.position;
    const division = pos.count - 1;
    const cycle = cycles || 1;
    const peak = peakHeight || 1;
    const radius = startPoint.length();
    const angle = startPoint.angleTo(endPoint);
    const arcLength = radius * angle;
    const diameterMinor = arcLength / Math.PI;
    const radiusMinor = (diameterMinor * 0.5) / cycle;
    const peakRatio = peak / diameterMinor;
    const radiusMajor = startPoint.length() + radiusMinor;
    const basisMajor = new THREE.Vector3().copy(startPoint).setLength(radiusMajor);
    const basisMinor = new THREE.Vector3().copy(startPoint).negate().setLength(radiusMinor);
    // triangle (start, end, center)
    const tri = new THREE.Triangle(startPoint, endPoint, new THREE.Vector3());
    const nrm = new THREE.Vector3(); // normal
    tri.getNormal(nrm);
    // rotate startPoint around normal
    const v3Major = new THREE.Vector3();
    const v3Minor = new THREE.Vector3();
    const v3Inter = new THREE.Vector3();
    const vFinal = new THREE.Vector3();
    for (let i = 0; i <= division; i++) {
        const divisionRatio = i / division;
        const angleValue = angle * divisionRatio;
        v3Major.copy(basisMajor).applyAxisAngle(nrm, angleValue);
        v3Minor.copy(basisMinor).applyAxisAngle(nrm, angleValue + Math.PI * 2 * divisionRatio * cycle);
        v3Inter.addVectors(v3Major, v3Minor);
        const newLength = ((v3Inter.length() - radius) * peakRatio) + radius;
        vFinal.copy(v3Inter).setLength(newLength);
        pos.setXYZ(i, vFinal.x, vFinal.y, vFinal.z);
    }
    pos.needsUpdate = true;
    l.computeLineDistances();
    l.geometry.attributes.lineDistance.needsUpdate = true;
    impacts[l.userData.idx].trailLength.value = l.geometry.attributes.lineDistance.array[99];
    l.material.dashSize = 7
}
function cTv(coordObj={lat:48.5125,lon:2.2055}){//coordinates to vector | Default: Paris
  const parisSpherical = {
    lat: THREE.Math.degToRad(90 - coordObj.lat),
    lon: THREE.Math.degToRad(coordObj.lon)
  };
  const radius = 5;// corresponds to the radius of the planet map
  const vector=new THREE.Vector3().setFromSphericalCoords(
    radius,
    parisSpherical.lat,
    parisSpherical.lon
  );
  return vector
}
scene.add(group) // Add a group that rotates: the map of the planet, "boom", lines, sticks

window.addEventListener( 'resize', onWindowResize )
// JFT
const stats = Stats()
document.body.appendChild(stats.dom)
// \ JFT
renderer.setAnimationLoop( () => {
  TWEEN.update()
  group.rotation.y += 0.001
  renderer.render(scene, camera)
  // JFT
  stats.update()
  // \ JFT
})

//Fix to compute canvas width/height
setTimeout(()=>{onWindowResize()},1)

function onWindowResize() {
  const sizes = {width: parseInt(window.getComputedStyle(cns).width),  height: parseInt(window.getComputedStyle(cns).height)};
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();
  renderer.setSize( sizes.width , sizes.height );
}

Расшифровка временных меток видео:

00:00 Hi
01:00 Что нового в планете?
04:11 Что от Вас требуется?
11:49 В чём были трудности?
14:40 Начинаю пояснять код
14:51 Зачем Stats на сцене с ThreeJS?
17:27 Особенности добавления TTF шрифта на сцену ThreeJS
19:30 Поменялся массив с данными
28:13 Как добавляется TTF шрифт на сцену ThreeJS и пишется текст?
41:45 createText — функция создания текста в Three.JS (самописная)
50:20 multiplyScalar в Three.JS, зачем?
57:27 Узнаём ширину и высоту 3D объекта в Three.JS
1:01:36 Создаём прямоугольник с закруглёнными углами
1:11:05 Создаём треугольник в Three.JS (большая ирония маленького треугольника)
1:15:46 Документация ThreeJS на русском
1:34:15 Bye-bye
copyleft 2023. we use: GatsbyJS, React, Google Analytics. Source code