mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
fix(globe): Navara aircraft models face, size and light as Cesium's (10b review 3)
The near-pose finding (Navara models faint, Cesium ~23 bright aircraft) came from the side-by-side's frozen page clock: the fleet tick ran once per pose, so Cesium never handed its aircraft to their models and drew 3× glyphs, against real models on Navara. With the hand-off driven (the move ends every 10 frames while a pose loads), both engines draw models, and the comparison showed three real differences, all fixed: - Orientation: Navara's glTF loader is three's GLTFLoader, which keeps the scene Y-up; the instance turn is now Cesium's whole axis correction (Y_UP_TO_Z_UP × Z_UP_TO_X_UP), not Z_UP_TO_X_UP alone. Every fleet model drew 90° off its heading. - Size: minimumPixelSize is taken at Cesium's distance (Camera distanceToBoundingSphere: view depth less the radius at the model's own scale), in the mover and in ModelHandle.computedScale; the straight-line distance drew off-centre models up to 16 % larger. - Colour: the instance colour tints by the set's blend amount (Cesium's colorBlendAmount) and its alpha is the model's opacity (Cesium: a model colour's alpha makes it translucent), not a blend weight; a model is translucent only while faded. An ambient share of the tinted surface (MODEL_AMBIENT 0.5, Cesium's image-based lighting) keeps a tinted aircraft near-white at a low sun (median luminance 139 → 190; Cesium 197). qa-flights-engines (now --layer=flights|military) gates the near pose with --models=on: computed scale within ±5 % per model (ratio 1.000007), footprint against the same view with the layer off within ±20 % (1553 vs 1496 px, 104 %), median luminance within ±10 % (189.8 vs 196.7). The probe's drawn() reports a model's computedScale. Flights models on: 19/19.
This commit is contained in:
1 parent
9dc446bd24
commit
10d0e49ddb
6 files changed
+158
-23
No files matched your search
@@ -72,6 +72,7 @@ class GevMoverSetDesc extends MeshDesc {
|
|||||||
// The camera a mover needs each drawn frame (scratch: no allocation).
|
// The camera a mover needs each drawn frame (scratch: no allocation).
|
||||||
const frameCamera = {
|
const frameCamera = {
|
||||||
cameraPosition: { x: 0, y: 0, z: 0 },
|
cameraPosition: { x: 0, y: 0, z: 0 },
|
||||||
|
cameraDirection: { x: 0, y: 0, z: -1 },
|
||||||
cameraHeight: 0,
|
cameraHeight: 0,
|
||||||
fovDeg: 60,
|
fovDeg: 60,
|
||||||
viewportHeightPx: 0,
|
viewportHeightPx: 0,
|
||||||
@@ -86,6 +87,12 @@ function updateMover(view, ctx, mover) {
|
|||||||
position.x = e[12];
|
position.x = e[12];
|
||||||
position.y = e[13];
|
position.y = e[13];
|
||||||
position.z = e[14];
|
position.z = e[14];
|
||||||
|
// A three camera looks down its local −z.
|
||||||
|
const direction = frameCamera.cameraDirection;
|
||||||
|
const length = Math.hypot(e[8], e[9], e[10]) || 1;
|
||||||
|
direction.x = -e[8] / length;
|
||||||
|
direction.y = -e[9] / length;
|
||||||
|
direction.z = -e[10] / length;
|
||||||
frameCamera.cameraHeight =
|
frameCamera.cameraHeight =
|
||||||
Math.hypot(position.x, position.y, position.z) - EARTH_RADIUS_M;
|
Math.hypot(position.x, position.y, position.z) - EARTH_RADIUS_M;
|
||||||
frameCamera.fovDeg = raw.fov;
|
frameCamera.fovDeg = raw.fov;
|
||||||
|
|||||||
@@ -19,12 +19,39 @@ import {
|
|||||||
} from 'three';
|
} from 'three';
|
||||||
import { footprintPerDepth, originFor } from './pack.js';
|
import { footprintPerDepth, originFor } from './pack.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ambient light on the tinted surface, as an emissive share of it: Cesium
|
||||||
|
* lights models with image-based lighting besides the sun, so a tinted
|
||||||
|
* aircraft reads near-white at any sun angle; Navara's sky light alone left
|
||||||
|
* them grey-brown at dawn. 0.5 matched Cesium's model luminance at the near
|
||||||
|
* pose (qa-flights-engines --models=on: median 190 vs 193 on Cesium; 139
|
||||||
|
* without it).
|
||||||
|
*/
|
||||||
|
export const MODEL_AMBIENT = 0.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The distance Cesium sizes a model's minimumPixelSize at (Camera
|
||||||
|
* distanceToBoundingSphere): |offset · view direction| less `radiusM`, at
|
||||||
|
* least 0; the straight-line distance when no direction is known.
|
||||||
|
*/
|
||||||
|
export function modelPixelDistance(dx, dy, dz, direction, radiusM) {
|
||||||
|
const along = direction
|
||||||
|
? Math.abs(dx * direction.x + dy * direction.y + dz * direction.z)
|
||||||
|
: Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
return Math.max(0, along - radiusM);
|
||||||
|
}
|
||||||
|
|
||||||
const scratchMatrix = new Matrix4();
|
const scratchMatrix = new Matrix4();
|
||||||
const scratchQuaternion = new Quaternion();
|
const scratchQuaternion = new Quaternion();
|
||||||
const scratchTranslation = new Vector3();
|
const scratchTranslation = new Vector3();
|
||||||
const scratchScale = new Vector3();
|
const scratchScale = new Vector3();
|
||||||
|
|
||||||
/** Mixes the instance colour into the material's colour by uGevBlend × alpha. */
|
/**
|
||||||
|
* Mixes the instance colour into the material's colour by uGevBlend (the
|
||||||
|
* set's colorBlend, as Cesium's colorBlendAmount); the instance alpha is the
|
||||||
|
* model's opacity (Cesium: a model colour's alpha makes it translucent; the
|
||||||
|
* aircraft fade with distance through it), not a blend weight.
|
||||||
|
*/
|
||||||
function blendInstanceColor(material, uniforms) {
|
function blendInstanceColor(material, uniforms) {
|
||||||
material.onBeforeCompile = (shader) => {
|
material.onBeforeCompile = (shader) => {
|
||||||
Object.assign(shader.uniforms, uniforms);
|
Object.assign(shader.uniforms, uniforms);
|
||||||
@@ -40,11 +67,15 @@ function blendInstanceColor(material, uniforms) {
|
|||||||
shader.fragmentShader = shader.fragmentShader
|
shader.fragmentShader = shader.fragmentShader
|
||||||
.replace(
|
.replace(
|
||||||
'void main() {',
|
'void main() {',
|
||||||
'uniform float uGevBlend;\nvarying vec4 vGevColor;\nvoid main() {',
|
'uniform float uGevBlend;\nuniform float uGevAmbient;\nvarying vec4 vGevColor;\nvoid main() {',
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'#include <emissivemap_fragment>',
|
||||||
|
'#include <emissivemap_fragment>\ntotalEmissiveRadiance += diffuseColor.rgb * uGevAmbient;',
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
'#include <map_fragment>',
|
'#include <map_fragment>',
|
||||||
'#include <map_fragment>\ndiffuseColor.rgb = mix(diffuseColor.rgb, vGevColor.rgb, uGevBlend * vGevColor.a);',
|
'#include <map_fragment>\ndiffuseColor.rgb = mix(diffuseColor.rgb, vGevColor.rgb, uGevBlend);\ndiffuseColor.a *= vGevColor.a;',
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
material.customProgramCacheKey = () => 'gev-mover-model-blend';
|
material.customProgramCacheKey = () => 'gev-mover-model-blend';
|
||||||
@@ -58,6 +89,7 @@ function blendInstanceColor(material, uniforms) {
|
|||||||
export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
||||||
const { capacity, model } = options;
|
const { capacity, model } = options;
|
||||||
const uniforms = {
|
const uniforms = {
|
||||||
|
uGevAmbient: { value: MODEL_AMBIENT },
|
||||||
uGevBlend: {
|
uGevBlend: {
|
||||||
value: model.colorBlend
|
value: model.colorBlend
|
||||||
? model.colorBlend.mode === 'replace'
|
? model.colorBlend.mode === 'replace'
|
||||||
@@ -71,9 +103,24 @@ export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
|||||||
let origin = null;
|
let origin = null;
|
||||||
let footprint = 0;
|
let footprint = 0;
|
||||||
let cameraPosition = null;
|
let cameraPosition = null;
|
||||||
|
let cameraDirection = null;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
const colorAttributes = [];
|
const colorAttributes = [];
|
||||||
|
const materials = [];
|
||||||
|
// Translucent only while an instance is faded (opaque models keep the
|
||||||
|
// opaque pass).
|
||||||
|
const syncTransparency = () => {
|
||||||
|
const { colors } = store;
|
||||||
|
let faded = false;
|
||||||
|
for (let index = 0; index < store.count && !faded; index++)
|
||||||
|
if (store.shows[index] === 1 && colors[index * 4 + 3] < 1) faded = true;
|
||||||
|
for (const material of materials)
|
||||||
|
if (material.transparent !== faded) {
|
||||||
|
material.transparent = faded;
|
||||||
|
material.needsUpdate = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const write = (from, to) => {
|
const write = (from, to) => {
|
||||||
if (!loaded || !origin) return;
|
if (!loaded || !origin) return;
|
||||||
@@ -84,7 +131,16 @@ export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
|||||||
const dx = positions[index * 3] - cameraPosition.x;
|
const dx = positions[index * 3] - cameraPosition.x;
|
||||||
const dy = positions[index * 3 + 1] - cameraPosition.y;
|
const dy = positions[index * 3 + 1] - cameraPosition.y;
|
||||||
const dz = positions[index * 3 + 2] - cameraPosition.z;
|
const dz = positions[index * 3 + 2] - cameraPosition.z;
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
// Cesium's distance for minimumPixelSize (Camera
|
||||||
|
// distanceToBoundingSphere): the depth along the view direction
|
||||||
|
// less the model's radius at its own scale.
|
||||||
|
const distance = modelPixelDistance(
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
dz,
|
||||||
|
cameraDirection,
|
||||||
|
radius * scale,
|
||||||
|
);
|
||||||
const pixels = (2 * radius * scale) / (footprint * distance);
|
const pixels = (2 * radius * scale) / (footprint * distance);
|
||||||
if (pixels < model.minimumPixelSize)
|
if (pixels < model.minimumPixelSize)
|
||||||
scale *= model.minimumPixelSize / pixels;
|
scale *= model.minimumPixelSize / pixels;
|
||||||
@@ -119,6 +175,7 @@ export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
|||||||
mesh.instanceMatrix.needsUpdate = true;
|
mesh.instanceMatrix.needsUpdate = true;
|
||||||
}
|
}
|
||||||
for (const attribute of colorAttributes) attribute.needsUpdate = true;
|
for (const attribute of colorAttributes) attribute.needsUpdate = true;
|
||||||
|
syncTransparency();
|
||||||
};
|
};
|
||||||
|
|
||||||
const group = { meshes: [], ready: null };
|
const group = { meshes: [], ready: null };
|
||||||
@@ -140,6 +197,7 @@ export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
|||||||
});
|
});
|
||||||
else material = material.clone();
|
else material = material.clone();
|
||||||
blendInstanceColor(material, uniforms);
|
blendInstanceColor(material, uniforms);
|
||||||
|
materials.push(material);
|
||||||
const geometry = child.geometry.clone();
|
const geometry = child.geometry.clone();
|
||||||
const color = new InstancedBufferAttribute(
|
const color = new InstancedBufferAttribute(
|
||||||
new Float32Array(capacity * 4).fill(1),
|
new Float32Array(capacity * 4).fill(1),
|
||||||
@@ -189,9 +247,16 @@ export function createModelMover(options, store, { load, onLoad = () => {} }) {
|
|||||||
for (const { mesh } of parts) mesh.count = store.count;
|
for (const { mesh } of parts) mesh.count = store.count;
|
||||||
},
|
},
|
||||||
/** Each drawn frame: pixel limits need every instance rewritten. */
|
/** Each drawn frame: pixel limits need every instance rewritten. */
|
||||||
update({ cameraPosition: camera, cameraHeight, fovDeg, viewportHeightPx }) {
|
update({
|
||||||
|
cameraPosition: camera,
|
||||||
|
cameraDirection: direction = null,
|
||||||
|
cameraHeight,
|
||||||
|
fovDeg,
|
||||||
|
viewportHeightPx,
|
||||||
|
}) {
|
||||||
footprint = footprintPerDepth(fovDeg, viewportHeightPx);
|
footprint = footprintPerDepth(fovDeg, viewportHeightPx);
|
||||||
cameraPosition = camera;
|
cameraPosition = camera;
|
||||||
|
cameraDirection = direction;
|
||||||
if (!origin || store.count === 0) return false;
|
if (!origin || store.count === 0) return false;
|
||||||
const next = originFor(camera, origin, cameraHeight);
|
const next = originFor(camera, origin, cameraHeight);
|
||||||
if (next !== origin) placeAt(next);
|
if (next !== origin) placeAt(next);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Vector3,
|
Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
import { createMoverStore, normalizeMoverSetOptions } from '../../movers.js';
|
import { createMoverStore, normalizeMoverSetOptions } from '../../movers.js';
|
||||||
import { createModelMover } from './modelMover.js';
|
import { createModelMover, modelPixelDistance } from './modelMover.js';
|
||||||
|
|
||||||
const options = normalizeMoverSetOptions({
|
const options = normalizeMoverSetOptions({
|
||||||
id: 'm',
|
id: 'm',
|
||||||
@@ -69,6 +69,7 @@ test('a far model keeps its minimum pixel size', async () => {
|
|||||||
await mover.group.ready;
|
await mover.group.ready;
|
||||||
mover.update({
|
mover.update({
|
||||||
cameraPosition: { x: 6_378_137 + 100_000, y: 0, z: 0 },
|
cameraPosition: { x: 6_378_137 + 100_000, y: 0, z: 0 },
|
||||||
|
cameraDirection: { x: -1, y: 0, z: 0 },
|
||||||
cameraHeight: 100_000,
|
cameraHeight: 100_000,
|
||||||
fovDeg: 60,
|
fovDeg: 60,
|
||||||
viewportHeightPx: 800,
|
viewportHeightPx: 800,
|
||||||
@@ -76,11 +77,57 @@ test('a far model keeps its minimum pixel size', async () => {
|
|||||||
const matrix = new Matrix4();
|
const matrix = new Matrix4();
|
||||||
mover.group.meshes[0].getMatrixAt(0, matrix);
|
mover.group.meshes[0].getMatrixAt(0, matrix);
|
||||||
const scale = new Vector3().setFromMatrixScale(matrix).x;
|
const scale = new Vector3().setFromMatrixScale(matrix).x;
|
||||||
// Box radius √3; 40 px at 100 km with a 60° fov over 800 px.
|
// Box radius √3; 40 px at 100 km (less the radius, as Cesium) with a 60°
|
||||||
|
// fov over 800 px.
|
||||||
const footprint = (2 * Math.tan(Math.PI / 6)) / 800;
|
const footprint = (2 * Math.tan(Math.PI / 6)) / 800;
|
||||||
const expected = (40 * footprint * 100_000) / (2 * Math.sqrt(3));
|
const expected =
|
||||||
|
(40 * footprint * (100_000 - Math.sqrt(3))) / (2 * Math.sqrt(3));
|
||||||
assert.ok(
|
assert.ok(
|
||||||
Math.abs(scale - expected) / expected < 1e-6,
|
Math.abs(scale - expected) / expected < 1e-6,
|
||||||
`${scale} vs ${expected}`,
|
`${scale} vs ${expected}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the instance colour tints by the set's blend amount; its alpha is opacity, as Cesium's model colour", async () => {
|
||||||
|
const store = createMoverStore(options);
|
||||||
|
const mover = createModelMover(options, store, {
|
||||||
|
load: async () => fakeGltf(),
|
||||||
|
});
|
||||||
|
store.set(0, {
|
||||||
|
position: { x: 6_378_137, y: 0, z: 0 },
|
||||||
|
color: [1, 1, 1, 0.4],
|
||||||
|
});
|
||||||
|
store.setCount(1);
|
||||||
|
mover.commit(store.dirty());
|
||||||
|
await mover.group.ready;
|
||||||
|
const [mesh] = mover.group.meshes;
|
||||||
|
const shader = {
|
||||||
|
uniforms: {},
|
||||||
|
vertexShader: 'void main() {\n#include <begin_vertex>\n}',
|
||||||
|
fragmentShader:
|
||||||
|
'void main() {\n#include <map_fragment>\n#include <emissivemap_fragment>\n}',
|
||||||
|
};
|
||||||
|
mesh.material.onBeforeCompile(shader);
|
||||||
|
assert.match(
|
||||||
|
shader.fragmentShader,
|
||||||
|
/diffuseColor\.rgb = mix\(diffuseColor\.rgb, vGevColor\.rgb, uGevBlend\);\s*diffuseColor\.a \*= vGevColor\.a;/,
|
||||||
|
);
|
||||||
|
// Ambient light on the tinted surface (Cesium's image-based lighting).
|
||||||
|
assert.match(
|
||||||
|
shader.fragmentShader,
|
||||||
|
/totalEmissiveRadiance \+= diffuseColor\.rgb \* uGevAmbient;/,
|
||||||
|
);
|
||||||
|
assert.equal(mover.uniforms.uGevAmbient.value, 0.5);
|
||||||
|
assert.equal(mesh.material.transparent, true, 'a faded instance');
|
||||||
|
store.set(0, { color: [1, 1, 1, 1] });
|
||||||
|
mover.commit(store.dirty());
|
||||||
|
assert.equal(mesh.material.transparent, false, 'all opaque again');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the minimum pixel size is taken at Cesium's distance: view depth less the radius", () => {
|
||||||
|
const ahead = { x: 0, y: 0, z: -1 };
|
||||||
|
// 3 km ahead and 4 km aside: depth 3 km, not the 5 km straight line.
|
||||||
|
assert.equal(modelPixelDistance(4000, 0, -3000, ahead, 30), 2970);
|
||||||
|
assert.equal(modelPixelDistance(0, 0, -10, ahead, 30), 0);
|
||||||
|
assert.equal(modelPixelDistance(4000, 0, -3000, null, 0), 5000);
|
||||||
|
});
|
||||||
@@ -30,7 +30,13 @@ export function aircraftQaDrawn(state) {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
model: model
|
model: model
|
||||||
? { scale: model.scale, show: model.show, ready: model.ready }
|
? {
|
||||||
|
scale: model.scale,
|
||||||
|
// The drawn scale, after the minimum pixel size.
|
||||||
|
computedScale: model.computedScale,
|
||||||
|
show: model.show,
|
||||||
|
ready: model.ready,
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,15 +27,15 @@ import { registerViewerEngine } from '../../../globe/cesium/engineFromViewer.js'
|
|||||||
const BASE_SIZE_PX = 20;
|
const BASE_SIZE_PX = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The model frame → glTF scene axes on Navara. Cesium draws a glTF through
|
* The model frame → glTF scene axes on Navara: Cesium's own glTF axis
|
||||||
* Y_UP_TO_Z_UP × Z_UP_TO_X_UP (data/modelVisualAnchor.js); Navara's glTF
|
* correction, Y_UP_TO_Z_UP × Z_UP_TO_X_UP (data/modelVisualAnchor.js).
|
||||||
* loader already turns the scene Y-up to Z-up, so here it is Z_UP_TO_X_UP
|
* Navara's glTF loader is three's GLTFLoader, which keeps the scene Y-up.
|
||||||
* alone: nose, top and heading of airplane.glb where Cesium draws them
|
* (10b used Z_UP_TO_X_UP alone, on the belief that the loader turned the
|
||||||
* (checked side by side with Cesium on a tracked aircraft at 300 m against
|
* scene Z-up: every fleet model drew 90° off its heading — side by side
|
||||||
* five other axis candidates).
|
* with Cesium at the near pose, 10b review item 3.)
|
||||||
*/
|
*/
|
||||||
// Cesium's Axis.Z_UP_TO_X_UP, column-major.
|
// Cesium's Axis.Y_UP_TO_Z_UP × Axis.Z_UP_TO_X_UP, column-major.
|
||||||
const NAVARA_GLTF_AXIS = [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1];
|
const NAVARA_GLTF_AXIS = [0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1];
|
||||||
const FIRST_CAPACITY = 1024;
|
const FIRST_CAPACITY = 1024;
|
||||||
const ATLAS_PAD = 2;
|
const ATLAS_PAD = 2;
|
||||||
const TRAIL_ALPHA = 0.85;
|
const TRAIL_ALPHA = 0.85;
|
||||||
@@ -644,6 +644,8 @@ export function createAircraftNavaraRenderer({
|
|||||||
return bank;
|
return bank;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scratchOffset = geo.cartesian3.create();
|
||||||
|
|
||||||
class ModelHandle {
|
class ModelHandle {
|
||||||
constructor(spec) {
|
constructor(spec) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
@@ -669,9 +671,13 @@ export function createAircraftNavaraRenderer({
|
|||||||
const minimum = this.spec.minimumPixelSize ?? 0;
|
const minimum = this.spec.minimumPixelSize ?? 0;
|
||||||
if (!(minimum > 0)) return this._scale;
|
if (!(minimum > 0)) return this._scale;
|
||||||
const cam = freshCamera();
|
const cam = freshCamera();
|
||||||
const distance = geo.cartesian3.distance(
|
// Cesium's distance (Camera distanceToBoundingSphere): the depth
|
||||||
cam.positionWC,
|
// along the view less the radius at the model's own scale.
|
||||||
this.translation,
|
geo.cartesian3.subtract(this.translation, cam.positionWC, scratchOffset);
|
||||||
|
const distance = Math.max(
|
||||||
|
0,
|
||||||
|
Math.abs(geo.cartesian3.dot(scratchOffset, cam.directionWC)) -
|
||||||
|
this.radiusM * this._scale,
|
||||||
);
|
);
|
||||||
const height = engine.host.size().height;
|
const height = engine.host.size().height;
|
||||||
if (!(distance > 0) || !(height > 0)) return this._scale;
|
if (!(distance > 0) || !(height > 0)) return this._scale;
|
||||||
|
|||||||
@@ -227,11 +227,15 @@ test("a model pose is the Cesium model frame, turned to Navara's glTF axes", asy
|
|||||||
);
|
);
|
||||||
for (let index = 0; index < 16; index++)
|
for (let index = 0; index < 16; index++)
|
||||||
assert.ok(Math.abs(model.modelMatrix[index] - expected[index]) < 1e-6);
|
assert.ok(Math.abs(model.modelMatrix[index] - expected[index]) < 1e-6);
|
||||||
// The instance orientation: modelMatrix × Cesium's Z_UP_TO_X_UP (Navara's
|
// The instance orientation: modelMatrix × Cesium's glTF axis correction
|
||||||
// loader already turned the glTF Z-up).
|
// (Y_UP_TO_Z_UP × Z_UP_TO_X_UP; Navara's loader keeps the glTF Y-up).
|
||||||
const corrected = Cesium.Matrix4.multiplyTransformation(
|
const corrected = Cesium.Matrix4.multiplyTransformation(
|
||||||
expected,
|
expected,
|
||||||
Cesium.Axis.Z_UP_TO_X_UP,
|
Cesium.Matrix4.multiplyTransformation(
|
||||||
|
Cesium.Axis.Y_UP_TO_Z_UP,
|
||||||
|
Cesium.Axis.Z_UP_TO_X_UP,
|
||||||
|
new Cesium.Matrix4(),
|
||||||
|
),
|
||||||
new Cesium.Matrix4(),
|
new Cesium.Matrix4(),
|
||||||
);
|
);
|
||||||
const q = Cesium.Quaternion.fromRotationMatrix(
|
const q = Cesium.Quaternion.fromRotationMatrix(
|
||||||
|
|||||||
Reference in new issue
Block a user