Load models directly from a Three.js loader (skinned mesh in this instance).

Giro3D version
THREE.js version
OpenLayers version
CRS
Memory usage (CPU)
Memory usage (GPU)
Frames
Clear color
Clear alpha
Status
Local clipping enabled
Capabilities
WebGL 2
Max texture units
Max texture size
Precision
Max fragment shader uniforms
Logarithmic depth buffer
Max shader attributes
Check shader errors
EXT_clip_control
EXT_color_buffer_float
EXT_color_buffer_half_float
EXT_conservative_depth
EXT_depth_clamp
EXT_float_blend
EXT_polygon_offset_clamp
EXT_texture_compression_bptc
EXT_texture_compression_rgtc
EXT_texture_filter_anisotropic
EXT_texture_mirror_clamp_to_edge
EXT_texture_norm16
NV_shader_noperspective_interpolation
OES_draw_buffers_indexed
OES_sample_variables
OES_shader_multisample_interpolation
OES_texture_float_linear
OVR_multiview2
WEBGL_clip_cull_distance
WEBGL_compressed_texture_astc
WEBGL_compressed_texture_etc
WEBGL_compressed_texture_etc1
WEBGL_compressed_texture_s3tc
WEBGL_compressed_texture_s3tc_srgb
WEBGL_debug_renderer_info
WEBGL_debug_shaders
WEBGL_lose_context
WEBGL_multi_draw
WEBGL_polygon_mode
WEBGL_stencil_texturing
MSAA
EDL
EDL Radius
EDL Strength
Inpainting
Inpainting steps
Inpainting depth contrib.
Point cloud occlusion
Type
FOV
Automatic plane computation
Far plane
Near plane
Max far plane
Min near plane
Width (pixels)
Height (pixels)
x
y
z
x
y
z
color
Enable cache
Default TTL (seconds)
Capacity (MB)
Capacity (entries)
Entries
Memory usage (approx)
Pending requests
Memory tracker
Show helpers
Show hidden objects
Name filter
Hierarchy
Properties
isObject3D
uuid
name
type
matrixAutoUpdate
matrixWorldAutoUpdate
matrixWorldNeedsUpdate
visible
castShadow
receiveShadow
frustumCulled
renderOrder
x
y
z
x
y
z
100% © threejs.org
index.js
import {
  AnimationMixer,
  DirectionalLight,
  Clock,
  Color,
  Fog,
  HemisphereLight,
  Mesh,
  MeshPhongMaterial,
  PlaneGeometry,
  Vector3,
} from "three";
import { MapControls } from "three/examples/jsm/controls/MapControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { clone } from "three/examples/jsm/utils/SkeletonUtils.js";

import Instance from "@giro3d/giro3d/core/Instance.js";
import Inspector from "@giro3d/giro3d/gui/Inspector.js";

function bindNumericalDropDown(id, onChange) {
  const element = document.getElementById(id);
  if (!(element instanceof HTMLSelectElement)) {
    throw new Error(
      "invalid binding element: expected HTMLSelectElement, got: " +
        element.constructor.name,
    );
  }

  element.onchange = () => {
    onChange(parseInt(element.value));
  };

  const callback = (v) => {
    element.value = v.toString();
    onChange(parseInt(element.value));
  };

  return [callback, parseInt(element.value), element];
}

const instance = new Instance({
  target: "view",
  crs: "EPSG:3857",
});

const camera = instance.view.camera;

instance.renderer.shadowMap.enabled = true;

const controls = new MapControls(instance.view.camera, instance.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.2;
instance.view.setControls(controls);

const clock = new Clock();

// we can access the THREE.js scene directly
instance.scene.background = new Color(0xa0a0a0);
instance.scene.fog = new Fog(0xa0a0a0, 10, 50);

// adding lights directly to scene is ok
const hemiLight = new HemisphereLight(0xffffff, 0x444444, 2);
hemiLight.position.set(0, 0, 20);
hemiLight.updateMatrixWorld();
instance.scene.add(hemiLight);

const dirLight = new DirectionalLight(0xffffff, 3);
dirLight.position.set(-3, 10, 10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 4;
dirLight.shadow.camera.bottom = -4;
dirLight.shadow.camera.left = -4;
dirLight.shadow.camera.right = 4;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 40;
instance.scene.add(dirLight);
instance.scene.add(dirLight.target);
dirLight.updateMatrixWorld();

// Let's now setup a "ground" to receive the shadows
const mesh = new Mesh(
  new PlaneGeometry(200, 200),
  new MeshPhongMaterial({ color: 0xcbcbcb, depthWrite: false }),
);
mesh.receiveShadow = true;
// Contrary to lights, every meshes should be added through `instance.add`, in order for Giro3D to
// be aware of them. Otherwise the objects will just disappear.
// For technical details, see how MainLoop.js calculates camera near and far.
instance.add(mesh);

// Let's load objects using one of the THREE loaders.
const loader = new GLTFLoader();
loader.load("https://threejs.org/examples/models/gltf/Soldier.glb", (gltf) => {
  gltf.scene.traverse((object) => {
    if (object.isMesh) {
      object.castShadow = true;
    }
  });

  // this code is virtually identical to this example:
  // https://threejs.org/examples/webgl_animation_multiple
  const model1 = clone(gltf.scene);
  const model2 = clone(gltf.scene);
  const model3 = clone(gltf.scene);
  const models = [model1, model2, model3];

  const mixer1 = new AnimationMixer(model1);
  const mixer2 = new AnimationMixer(model2);
  const mixer3 = new AnimationMixer(model3);
  const mixers = [mixer1, mixer2, mixer3];

  mixer1.clipAction(gltf.animations[0]).play(); // idle
  mixer2.clipAction(gltf.animations[1]).play(); // run
  mixer3.clipAction(gltf.animations[3]).play(); // walk

  model1.position.x = 1;
  model1.rotation.x = Math.PI / 2;
  model1.updateMatrixWorld();
  model2.position.x = 0;
  model2.rotation.x = Math.PI / 2;
  model2.updateMatrixWorld();
  model3.position.x = 2;
  model3.rotation.x = Math.PI / 2;
  model3.updateMatrixWorld();

  // except for this part, we add directly to instance to make Giro3D aware of these models
  instance.add(model1);
  instance.add(model2);
  instance.add(model3);

  // let's move our camera and control target
  // We add 1 to z to look at the waist. The 0, 0, 0 is located at the soldier's feet.
  const lookAt = new Vector3(0, 0, 1).add(model1.position);
  camera.position.set(2, 6, 3);
  camera.lookAt(lookAt);
  controls.target.copy(lookAt);
  controls.saveState();

  // you can hook yourself to event of the rendering loop.
  instance.addEventListener("after-camera-update", () => {
    const delta = clock.getDelta();

    for (const mixer of mixers) {
      mixer.update(delta);
    }
    for (const model of models) {
      model.updateMatrixWorld();
      instance.notifyChange(model);
    }
  });

  instance.notifyChange();

  let where = models;
  bindNumericalDropDown("pick_source", (newMode) => {
    if (newMode === 1) {
      where = [model1];
    } else if (newMode === 2) {
      where = [model2];
    } else if (newMode === 3) {
      where = [model3];
    } else {
      where = models;
    }
  });

  const formatter = new Intl.NumberFormat();

  function format(point) {
    return `x: ${formatter.format(point.x)}\n
                y: ${formatter.format(point.y)}\n
                z: ${formatter.format(point.z)}`;
  }

  instance.domElement.addEventListener("dblclick", (e) => {
    const picked = instance.pickObjectsAt(e, { limit: 1, where });
    if (picked.length === 0) {
      document.getElementById("selectedDiv").innerText = "No object found";
    } else {
      document.getElementById("selectedDiv").innerHTML = `
                Picked ${picked[0].object.name} at:<br>
                ${format(picked[0].point)}!`;
    }
  });
});

Inspector.attach("inspector", instance);
index.html
<!doctype html>
<html lang="en">
  <head>
    <title>Three.js loaders</title>
    <meta charset="UTF-8" />
    <meta name="name" content="three_loader" />
    <meta
      name="description"
      content="Load models directly from a Three.js loader (skinned mesh in this instance)."
    />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <link rel="icon" href="https://giro3d.org/images/favicon.svg" />
    <link
      href="https://giro3d.org/assets/bootstrap-custom.css"
      rel="stylesheet"
    />
    <script src="https://giro3d.org/assets/bootstrap.bundle.min.js"></script>
    <link
      rel="stylesheet"
      type="text/css"
      href="https://giro3d.org/latest/examples/css/example.css"
    />
  </head>

  <body>
    <div id="view" class="m-0 p-0 w-100 h-100"></div>
    <div
      id="inspector"
      class="position-absolute top-0 start-0 mh-100 overflow-auto"
    ></div>

    <div class="side-pane-with-status-bar">
      <div class="card">
        <div class="card-body">
          <div class="input-group">
            <label class="input-group-text" for="pick_source">Pick from</label>
            <select class="form-select" id="pick_source">
              <option value="0" selected>All models</option>
              <option value="1">Vanguard idle</option>
              <option value="3">Vanguard walking</option>
              <option value="2">Vanguard running</option>
            </select>
          </div>
          <div id="selectedDiv"></div>
        </div>
      </div>
    </div>

    <script type="module" src="index.js"></script>
    <script>
      /* activate popovers */
      const popoverTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="popover"]'),
      );
      popoverTriggerList.map(
        // bootstrap is used as script in the template, disable warning about undef
        // eslint-disable-next-line no-undef
        (popoverTriggerEl) =>
          new bootstrap.Popover(popoverTriggerEl, {
            trigger: "hover",
            placement: "left",
            content: document.getElementById(
              popoverTriggerEl.getAttribute("data-bs-content"),
            ).innerHTML,
            html: true,
          }),
      );
    </script>
  </body>
</html>
package.json
{
    "name": "three_loader",
    "dependencies": {
        "@giro3d/giro3d": "0.42.3"
    },
    "devDependencies": {
        "vite": "^3.2.3"
    },
    "scripts": {
        "start": "vite",
        "build": "vite build"
    }
}