reze-engine 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine.d.ts +24 -32
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +374 -486
- package/dist/model.d.ts +9 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +20 -24
- package/package.json +1 -1
- package/src/engine.ts +393 -592
- package/src/model.ts +28 -28
package/dist/engine.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Camera } from "./camera";
|
|
2
2
|
import { Mat4, Vec3 } from "./math";
|
|
3
|
+
import { Physics } from "./physics";
|
|
3
4
|
export const DEFAULT_ENGINE_OPTIONS = {
|
|
4
5
|
ambientColor: new Vec3(0.88, 0.88, 0.88),
|
|
5
6
|
directionalLightIntensity: 0.24,
|
|
@@ -9,8 +10,6 @@ export const DEFAULT_ENGINE_OPTIONS = {
|
|
|
9
10
|
cameraTarget: new Vec3(0, 12.5, 0),
|
|
10
11
|
cameraFov: Math.PI / 4,
|
|
11
12
|
onRaycast: undefined,
|
|
12
|
-
disableIK: false,
|
|
13
|
-
disablePhysics: false,
|
|
14
13
|
multisampleCount: 4,
|
|
15
14
|
};
|
|
16
15
|
export class Engine {
|
|
@@ -31,26 +30,17 @@ export class Engine {
|
|
|
31
30
|
this.groundHasReflections = false;
|
|
32
31
|
this.groundMode = "reflection";
|
|
33
32
|
this.shadowLightVPMatrix = new Float32Array(16);
|
|
34
|
-
this.
|
|
33
|
+
this.groundDrawCall = null;
|
|
35
34
|
this.shadowVPLightX = Number.NaN;
|
|
36
35
|
this.shadowVPLightY = Number.NaN;
|
|
37
36
|
this.shadowVPLightZ = Number.NaN;
|
|
38
|
-
this.cachedSkinMatricesVersion = -1;
|
|
39
|
-
this.skinMatricesVersion = 0;
|
|
40
37
|
// Double-tap detection
|
|
41
38
|
this.lastTouchTime = 0;
|
|
42
39
|
this.DOUBLE_TAP_DELAY = 300; // ms
|
|
43
|
-
|
|
44
|
-
this._disableIK = false;
|
|
45
|
-
this._disablePhysics = false;
|
|
46
|
-
this.currentModel = null;
|
|
47
|
-
this.modelDir = "";
|
|
40
|
+
this.modelInstances = new Map();
|
|
48
41
|
this.textureCache = new Map();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.drawCalls = [];
|
|
52
|
-
// Material visibility tracking
|
|
53
|
-
this.hiddenMaterials = new Set();
|
|
42
|
+
/** Reusable buffer for raycast skinning to avoid per-instance allocations (Three.js/Babylon.js style). */
|
|
43
|
+
this.raycastVertexBuffer = null;
|
|
54
44
|
this.lastFpsUpdate = performance.now();
|
|
55
45
|
this.framesSinceLastUpdate = 0;
|
|
56
46
|
this.lastFrameTime = performance.now();
|
|
@@ -63,15 +53,13 @@ export class Engine {
|
|
|
63
53
|
this.animationFrameId = null;
|
|
64
54
|
this.renderLoopCallback = null;
|
|
65
55
|
this.handleCanvasDoubleClick = (event) => {
|
|
66
|
-
if (!this.onRaycast ||
|
|
56
|
+
if (!this.onRaycast || this.modelInstances.size === 0)
|
|
67
57
|
return;
|
|
68
58
|
const rect = this.canvas.getBoundingClientRect();
|
|
69
|
-
|
|
70
|
-
const y = event.clientY - rect.top;
|
|
71
|
-
this.performRaycast(x, y);
|
|
59
|
+
this.performRaycast(event.clientX - rect.left, event.clientY - rect.top);
|
|
72
60
|
};
|
|
73
61
|
this.handleCanvasTouch = (event) => {
|
|
74
|
-
if (!this.onRaycast ||
|
|
62
|
+
if (!this.onRaycast || this.modelInstances.size === 0)
|
|
75
63
|
return;
|
|
76
64
|
// Prevent default to avoid triggering mouse events
|
|
77
65
|
event.preventDefault();
|
|
@@ -107,8 +95,6 @@ export class Engine {
|
|
|
107
95
|
this.cameraTarget = options.cameraTarget ?? DEFAULT_ENGINE_OPTIONS.cameraTarget;
|
|
108
96
|
this.cameraFov = options.cameraFov ?? DEFAULT_ENGINE_OPTIONS.cameraFov;
|
|
109
97
|
this.onRaycast = options.onRaycast;
|
|
110
|
-
this._disableIK = options.disableIK ?? DEFAULT_ENGINE_OPTIONS.disableIK;
|
|
111
|
-
this._disablePhysics = options.disablePhysics ?? DEFAULT_ENGINE_OPTIONS.disablePhysics;
|
|
112
98
|
}
|
|
113
99
|
}
|
|
114
100
|
// Step 1: Get WebGPU device and context
|
|
@@ -968,7 +954,6 @@ export class Engine {
|
|
|
968
954
|
...options,
|
|
969
955
|
};
|
|
970
956
|
this.groundMode = opts.mode;
|
|
971
|
-
this.drawCalls = this.drawCalls.filter((d) => d.type !== "ground");
|
|
972
957
|
this.createGroundGeometry(opts.width, opts.height);
|
|
973
958
|
if (opts.mode === "reflection") {
|
|
974
959
|
this.createGroundMaterialBuffer(opts.diffuseColor, opts.reflectionLevel, opts.fadeStart, opts.fadeEnd);
|
|
@@ -978,13 +963,13 @@ export class Engine {
|
|
|
978
963
|
this.createShadowGroundResources(opts.shadowMapSize, opts.diffuseColor, opts.fadeStart, opts.fadeEnd, opts.shadowStrength);
|
|
979
964
|
}
|
|
980
965
|
this.groundHasReflections = true;
|
|
981
|
-
this.
|
|
966
|
+
this.groundDrawCall = {
|
|
982
967
|
type: "ground",
|
|
983
968
|
count: 6,
|
|
984
969
|
firstIndex: 0,
|
|
985
970
|
bindGroup: (opts.mode === "reflection" ? this.groundReflectionBindGroup : this.groundShadowBindGroup),
|
|
986
971
|
materialName: "Ground",
|
|
987
|
-
}
|
|
972
|
+
};
|
|
988
973
|
}
|
|
989
974
|
updateLightBuffer() {
|
|
990
975
|
this.device.queue.writeBuffer(this.lightUniformBuffer, 0, this.lightData);
|
|
@@ -1012,7 +997,7 @@ export class Engine {
|
|
|
1012
997
|
}
|
|
1013
998
|
dispose() {
|
|
1014
999
|
this.stopRenderLoop();
|
|
1015
|
-
this.
|
|
1000
|
+
this.forEachInstance((inst) => inst.model.stopAnimation());
|
|
1016
1001
|
if (Engine.instance === this)
|
|
1017
1002
|
Engine.instance = null;
|
|
1018
1003
|
if (this.camera)
|
|
@@ -1027,116 +1012,175 @@ export class Engine {
|
|
|
1027
1012
|
this.resizeObserver = null;
|
|
1028
1013
|
}
|
|
1029
1014
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1015
|
+
async addModel(model, pmxPath, name) {
|
|
1016
|
+
const requested = name ?? model.name;
|
|
1017
|
+
let key = requested;
|
|
1018
|
+
let n = 1;
|
|
1019
|
+
while (this.modelInstances.has(key)) {
|
|
1020
|
+
key = `${requested}_${n++}`;
|
|
1021
|
+
}
|
|
1032
1022
|
const pathParts = pmxPath.split("/");
|
|
1033
1023
|
pathParts.pop();
|
|
1034
|
-
|
|
1035
|
-
this.
|
|
1036
|
-
|
|
1037
|
-
await this.setupModelBuffers(model);
|
|
1024
|
+
const basePath = pathParts.join("/") + "/";
|
|
1025
|
+
await this.setupModelInstance(key, model, basePath);
|
|
1026
|
+
return key;
|
|
1038
1027
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
this.vertexBufferNeedsUpdate = true;
|
|
1028
|
+
async registerModel(model, pmxPath) {
|
|
1029
|
+
return this.addModel(model, pmxPath);
|
|
1042
1030
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
this.hiddenMaterials.add(name);
|
|
1049
|
-
}
|
|
1031
|
+
removeModel(name) {
|
|
1032
|
+
this.modelInstances.delete(name);
|
|
1033
|
+
}
|
|
1034
|
+
getModelNames() {
|
|
1035
|
+
return Array.from(this.modelInstances.keys());
|
|
1050
1036
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1037
|
+
getModel(name) {
|
|
1038
|
+
return this.modelInstances.get(name)?.model ?? null;
|
|
1039
|
+
}
|
|
1040
|
+
markVertexBufferDirty(modelNameOrModel) {
|
|
1041
|
+
if (modelNameOrModel === undefined)
|
|
1042
|
+
return;
|
|
1043
|
+
if (typeof modelNameOrModel === "string") {
|
|
1044
|
+
const inst = this.modelInstances.get(modelNameOrModel);
|
|
1045
|
+
if (inst)
|
|
1046
|
+
inst.vertexBufferNeedsUpdate = true;
|
|
1047
|
+
return;
|
|
1054
1048
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1049
|
+
for (const inst of this.modelInstances.values()) {
|
|
1050
|
+
if (inst.model === modelNameOrModel) {
|
|
1051
|
+
inst.vertexBufferNeedsUpdate = true;
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1057
1054
|
}
|
|
1058
1055
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1056
|
+
setMaterialVisible(modelName, materialName, visible) {
|
|
1057
|
+
const inst = this.modelInstances.get(modelName);
|
|
1058
|
+
if (!inst)
|
|
1059
|
+
return;
|
|
1060
|
+
if (visible)
|
|
1061
|
+
inst.hiddenMaterials.delete(materialName);
|
|
1062
|
+
else
|
|
1063
|
+
inst.hiddenMaterials.add(materialName);
|
|
1064
|
+
}
|
|
1065
|
+
toggleMaterialVisible(modelName, materialName) {
|
|
1066
|
+
const inst = this.modelInstances.get(modelName);
|
|
1067
|
+
if (!inst)
|
|
1068
|
+
return;
|
|
1069
|
+
if (inst.hiddenMaterials.has(materialName))
|
|
1070
|
+
inst.hiddenMaterials.delete(materialName);
|
|
1071
|
+
else
|
|
1072
|
+
inst.hiddenMaterials.add(materialName);
|
|
1061
1073
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
return
|
|
1074
|
+
isMaterialVisible(modelName, materialName) {
|
|
1075
|
+
const inst = this.modelInstances.get(modelName);
|
|
1076
|
+
return inst ? !inst.hiddenMaterials.has(materialName) : false;
|
|
1065
1077
|
}
|
|
1066
|
-
|
|
1067
|
-
this.
|
|
1068
|
-
this.currentModel?.setIKEnabled(!value);
|
|
1078
|
+
setModelIKEnabled(modelName, enabled) {
|
|
1079
|
+
this.modelInstances.get(modelName)?.model.setIKEnabled(enabled);
|
|
1069
1080
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
return this._disablePhysics;
|
|
1081
|
+
setModelPhysicsEnabled(modelName, enabled) {
|
|
1082
|
+
this.modelInstances.get(modelName)?.model.setPhysicsEnabled(enabled);
|
|
1073
1083
|
}
|
|
1074
|
-
|
|
1075
|
-
this.
|
|
1076
|
-
|
|
1084
|
+
resetPhysics() {
|
|
1085
|
+
this.forEachInstance((inst) => {
|
|
1086
|
+
if (!inst.physics)
|
|
1087
|
+
return;
|
|
1088
|
+
inst.model.computeWorldMatrices();
|
|
1089
|
+
inst.physics.reset(inst.model.getWorldMatrices(), inst.model.getBoneInverseBindMatrices());
|
|
1090
|
+
});
|
|
1077
1091
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1092
|
+
instances() {
|
|
1093
|
+
return this.modelInstances.values();
|
|
1094
|
+
}
|
|
1095
|
+
forEachInstance(fn) {
|
|
1096
|
+
for (const inst of this.instances())
|
|
1097
|
+
fn(inst);
|
|
1098
|
+
}
|
|
1099
|
+
updateInstances(deltaTime) {
|
|
1100
|
+
this.forEachInstance((inst) => {
|
|
1101
|
+
const verticesChanged = inst.model.update(deltaTime);
|
|
1102
|
+
if (verticesChanged)
|
|
1103
|
+
inst.vertexBufferNeedsUpdate = true;
|
|
1104
|
+
if (inst.physics && inst.model.getPhysicsEnabled()) {
|
|
1105
|
+
inst.physics.step(deltaTime, inst.model.getWorldMatrices(), inst.model.getBoneInverseBindMatrices());
|
|
1106
|
+
}
|
|
1107
|
+
if (inst.vertexBufferNeedsUpdate)
|
|
1108
|
+
this.updateVertexBuffer(inst);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
updateVertexBuffer(inst) {
|
|
1112
|
+
const vertices = inst.model.getVertices();
|
|
1113
|
+
if (!vertices?.length)
|
|
1083
1114
|
return;
|
|
1084
|
-
this.device.queue.writeBuffer(
|
|
1115
|
+
this.device.queue.writeBuffer(inst.vertexBuffer, 0, vertices);
|
|
1116
|
+
inst.vertexBufferNeedsUpdate = false;
|
|
1085
1117
|
}
|
|
1086
|
-
|
|
1087
|
-
async setupModelBuffers(model) {
|
|
1088
|
-
this.currentModel = model;
|
|
1089
|
-
// Apply IK and Physics flags from engine options
|
|
1090
|
-
model.setIKEnabled(!this._disableIK);
|
|
1091
|
-
model.setPhysicsEnabled(!this._disablePhysics);
|
|
1118
|
+
async setupModelInstance(name, model, basePath) {
|
|
1092
1119
|
const vertices = model.getVertices();
|
|
1093
1120
|
const skinning = model.getSkinning();
|
|
1094
1121
|
const skeleton = model.getSkeleton();
|
|
1095
|
-
|
|
1096
|
-
|
|
1122
|
+
const boneCount = skeleton.bones.length;
|
|
1123
|
+
const matrixSize = boneCount * 16 * 4;
|
|
1124
|
+
const vertexBuffer = this.device.createBuffer({
|
|
1125
|
+
label: `${name}: vertex buffer`,
|
|
1097
1126
|
size: vertices.byteLength,
|
|
1098
1127
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
1099
1128
|
});
|
|
1100
|
-
this.device.queue.writeBuffer(
|
|
1101
|
-
|
|
1102
|
-
label:
|
|
1129
|
+
this.device.queue.writeBuffer(vertexBuffer, 0, vertices);
|
|
1130
|
+
const jointsBuffer = this.device.createBuffer({
|
|
1131
|
+
label: `${name}: joints buffer`,
|
|
1103
1132
|
size: skinning.joints.byteLength,
|
|
1104
1133
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
1105
1134
|
});
|
|
1106
|
-
this.device.queue.writeBuffer(
|
|
1107
|
-
|
|
1108
|
-
label:
|
|
1135
|
+
this.device.queue.writeBuffer(jointsBuffer, 0, skinning.joints.buffer, skinning.joints.byteOffset, skinning.joints.byteLength);
|
|
1136
|
+
const weightsBuffer = this.device.createBuffer({
|
|
1137
|
+
label: `${name}: weights buffer`,
|
|
1109
1138
|
size: skinning.weights.byteLength,
|
|
1110
1139
|
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
1111
1140
|
});
|
|
1112
|
-
this.device.queue.writeBuffer(
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
this.skinMatrixBuffer = this.device.createBuffer({
|
|
1116
|
-
label: "skin matrices",
|
|
1141
|
+
this.device.queue.writeBuffer(weightsBuffer, 0, skinning.weights.buffer, skinning.weights.byteOffset, skinning.weights.byteLength);
|
|
1142
|
+
const skinMatrixBuffer = this.device.createBuffer({
|
|
1143
|
+
label: `${name}: skin matrices`,
|
|
1117
1144
|
size: Math.max(256, matrixSize),
|
|
1118
1145
|
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
1119
1146
|
});
|
|
1120
|
-
this.inverseBindMatrixBuffer = this.device.createBuffer({
|
|
1121
|
-
label: "inverse bind matrices",
|
|
1122
|
-
size: Math.max(256, matrixSize),
|
|
1123
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
1124
|
-
});
|
|
1125
|
-
const invBindMatrices = skeleton.inverseBindMatrices;
|
|
1126
|
-
this.device.queue.writeBuffer(this.inverseBindMatrixBuffer, 0, invBindMatrices.buffer, invBindMatrices.byteOffset, invBindMatrices.byteLength);
|
|
1127
1147
|
const indices = model.getIndices();
|
|
1128
|
-
if (indices)
|
|
1129
|
-
this.indexBuffer = this.device.createBuffer({
|
|
1130
|
-
label: "model index buffer",
|
|
1131
|
-
size: indices.byteLength,
|
|
1132
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
1133
|
-
});
|
|
1134
|
-
this.device.queue.writeBuffer(this.indexBuffer, 0, indices);
|
|
1135
|
-
}
|
|
1136
|
-
else {
|
|
1148
|
+
if (!indices)
|
|
1137
1149
|
throw new Error("Model has no index buffer");
|
|
1138
|
-
|
|
1139
|
-
|
|
1150
|
+
const indexBuffer = this.device.createBuffer({
|
|
1151
|
+
label: `${name}: index buffer`,
|
|
1152
|
+
size: indices.byteLength,
|
|
1153
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
1154
|
+
});
|
|
1155
|
+
this.device.queue.writeBuffer(indexBuffer, 0, indices);
|
|
1156
|
+
const rbs = model.getRigidbodies();
|
|
1157
|
+
const physics = rbs.length > 0 ? new Physics(rbs, model.getJoints()) : null;
|
|
1158
|
+
const shadowBindGroup = this.device.createBindGroup({
|
|
1159
|
+
label: `${name}: shadow bind`,
|
|
1160
|
+
layout: this.shadowDepthPipeline.getBindGroupLayout(0),
|
|
1161
|
+
entries: [
|
|
1162
|
+
{ binding: 0, resource: { buffer: this.shadowLightVPBuffer } },
|
|
1163
|
+
{ binding: 1, resource: { buffer: skinMatrixBuffer } },
|
|
1164
|
+
],
|
|
1165
|
+
});
|
|
1166
|
+
const inst = {
|
|
1167
|
+
name,
|
|
1168
|
+
model,
|
|
1169
|
+
basePath,
|
|
1170
|
+
vertexBuffer,
|
|
1171
|
+
indexBuffer,
|
|
1172
|
+
jointsBuffer,
|
|
1173
|
+
weightsBuffer,
|
|
1174
|
+
skinMatrixBuffer,
|
|
1175
|
+
drawCalls: [],
|
|
1176
|
+
shadowDrawCalls: [],
|
|
1177
|
+
shadowBindGroup,
|
|
1178
|
+
hiddenMaterials: new Set(),
|
|
1179
|
+
physics,
|
|
1180
|
+
vertexBufferNeedsUpdate: false,
|
|
1181
|
+
};
|
|
1182
|
+
await this.setupMaterialsForInstance(inst);
|
|
1183
|
+
this.modelInstances.set(name, inst);
|
|
1140
1184
|
}
|
|
1141
1185
|
createGroundGeometry(width = 100, height = 100) {
|
|
1142
1186
|
const halfWidth = width / 2;
|
|
@@ -1271,14 +1315,6 @@ export class Engine {
|
|
|
1271
1315
|
gb[7] = 0;
|
|
1272
1316
|
this.groundShadowMaterialBuffer = this.device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
1273
1317
|
this.device.queue.writeBuffer(this.groundShadowMaterialBuffer, 0, gb);
|
|
1274
|
-
this.shadowBindGroup = this.device.createBindGroup({
|
|
1275
|
-
label: "shadow bind",
|
|
1276
|
-
layout: this.shadowDepthPipeline.getBindGroupLayout(0),
|
|
1277
|
-
entries: [
|
|
1278
|
-
{ binding: 0, resource: { buffer: this.shadowLightVPBuffer } },
|
|
1279
|
-
{ binding: 1, resource: { buffer: this.skinMatrixBuffer } },
|
|
1280
|
-
],
|
|
1281
|
-
});
|
|
1282
1318
|
this.groundShadowBindGroup = this.device.createBindGroup({
|
|
1283
1319
|
label: "ground shadow bind",
|
|
1284
1320
|
layout: this.groundShadowBindGroupLayout,
|
|
@@ -1317,21 +1353,19 @@ export class Engine {
|
|
|
1317
1353
|
this.shadowLightVPMatrix.set(vp.values);
|
|
1318
1354
|
this.device.queue.writeBuffer(this.shadowLightVPBuffer, 0, this.shadowLightVPMatrix);
|
|
1319
1355
|
}
|
|
1320
|
-
async
|
|
1356
|
+
async setupMaterialsForInstance(inst) {
|
|
1357
|
+
const model = inst.model;
|
|
1321
1358
|
const materials = model.getMaterials();
|
|
1322
|
-
if (materials.length === 0)
|
|
1359
|
+
if (materials.length === 0)
|
|
1323
1360
|
throw new Error("Model has no materials");
|
|
1324
|
-
}
|
|
1325
1361
|
const textures = model.getTextures();
|
|
1362
|
+
const prefix = `${inst.name}: `;
|
|
1326
1363
|
const loadTextureByIndex = async (texIndex) => {
|
|
1327
|
-
if (texIndex < 0 || texIndex >= textures.length)
|
|
1364
|
+
if (texIndex < 0 || texIndex >= textures.length)
|
|
1328
1365
|
return null;
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
const texture = await this.createTextureFromPath(path);
|
|
1332
|
-
return texture;
|
|
1366
|
+
const path = inst.basePath + textures[texIndex].path;
|
|
1367
|
+
return this.createTextureFromPath(path);
|
|
1333
1368
|
};
|
|
1334
|
-
this.drawCalls = [];
|
|
1335
1369
|
let currentIndexOffset = 0;
|
|
1336
1370
|
for (const mat of materials) {
|
|
1337
1371
|
const indexCount = mat.vertexCount;
|
|
@@ -1342,128 +1376,86 @@ export class Engine {
|
|
|
1342
1376
|
throw new Error(`Material "${mat.name}" has no diffuse texture`);
|
|
1343
1377
|
const materialAlpha = mat.diffuse[3];
|
|
1344
1378
|
const isTransparent = materialAlpha < 1.0 - 0.001;
|
|
1345
|
-
const materialUniformBuffer = this.createMaterialUniformBuffer(mat.name, materialAlpha, 0.0, [mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]], mat.ambient, mat.specular, mat.shininess);
|
|
1346
|
-
// Create bind groups using the shared bind group layout - All pipelines (main, eye, hair multiply, hair opaque) use the same shader and layout
|
|
1379
|
+
const materialUniformBuffer = this.createMaterialUniformBuffer(prefix + mat.name, materialAlpha, 0.0, [mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]], mat.ambient, mat.specular, mat.shininess);
|
|
1347
1380
|
const bindGroup = this.device.createBindGroup({
|
|
1348
|
-
label:
|
|
1381
|
+
label: `${prefix}material: ${mat.name}`,
|
|
1349
1382
|
layout: this.mainBindGroupLayout,
|
|
1350
1383
|
entries: [
|
|
1351
1384
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1352
1385
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1353
1386
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1354
1387
|
{ binding: 3, resource: this.materialSampler },
|
|
1355
|
-
{ binding: 4, resource: { buffer:
|
|
1388
|
+
{ binding: 4, resource: { buffer: inst.skinMatrixBuffer } },
|
|
1356
1389
|
{ binding: 5, resource: { buffer: materialUniformBuffer } },
|
|
1357
1390
|
],
|
|
1358
1391
|
});
|
|
1359
1392
|
if (indexCount > 0) {
|
|
1360
1393
|
if (mat.isEye) {
|
|
1361
|
-
|
|
1362
|
-
type: "eye",
|
|
1363
|
-
count: indexCount,
|
|
1364
|
-
firstIndex: currentIndexOffset,
|
|
1365
|
-
bindGroup,
|
|
1366
|
-
materialName: mat.name,
|
|
1367
|
-
});
|
|
1394
|
+
inst.drawCalls.push({ type: "eye", count: indexCount, firstIndex: currentIndexOffset, bindGroup, materialName: mat.name });
|
|
1368
1395
|
}
|
|
1369
1396
|
else if (mat.isHair) {
|
|
1370
|
-
// Hair materials: create separate bind groups for over-eyes vs over-non-eyes
|
|
1371
1397
|
const createHairBindGroup = (isOverEyes) => {
|
|
1372
|
-
const
|
|
1398
|
+
const buf = this.createMaterialUniformBuffer(`${prefix}${mat.name} (${isOverEyes ? "over eyes" : "over non-eyes"})`, materialAlpha, isOverEyes ? 1.0 : 0.0, [mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]], mat.ambient, mat.specular, mat.shininess);
|
|
1373
1399
|
return this.device.createBindGroup({
|
|
1374
|
-
label:
|
|
1400
|
+
label: `${prefix}hair ${isOverEyes ? "over eyes" : "over non-eyes"}: ${mat.name}`,
|
|
1375
1401
|
layout: this.mainBindGroupLayout,
|
|
1376
1402
|
entries: [
|
|
1377
1403
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1378
1404
|
{ binding: 1, resource: { buffer: this.lightUniformBuffer } },
|
|
1379
1405
|
{ binding: 2, resource: diffuseTexture.createView() },
|
|
1380
1406
|
{ binding: 3, resource: this.materialSampler },
|
|
1381
|
-
{ binding: 4, resource: { buffer:
|
|
1382
|
-
{ binding: 5, resource: { buffer:
|
|
1407
|
+
{ binding: 4, resource: { buffer: inst.skinMatrixBuffer } },
|
|
1408
|
+
{ binding: 5, resource: { buffer: buf } },
|
|
1383
1409
|
],
|
|
1384
1410
|
});
|
|
1385
1411
|
};
|
|
1386
|
-
|
|
1387
|
-
const bindGroupOverNonEyes = createHairBindGroup(false);
|
|
1388
|
-
this.drawCalls.push({
|
|
1412
|
+
inst.drawCalls.push({
|
|
1389
1413
|
type: "hair-over-eyes",
|
|
1390
1414
|
count: indexCount,
|
|
1391
1415
|
firstIndex: currentIndexOffset,
|
|
1392
|
-
bindGroup:
|
|
1416
|
+
bindGroup: createHairBindGroup(true),
|
|
1393
1417
|
materialName: mat.name,
|
|
1394
1418
|
});
|
|
1395
|
-
|
|
1419
|
+
inst.drawCalls.push({
|
|
1396
1420
|
type: "hair-over-non-eyes",
|
|
1397
1421
|
count: indexCount,
|
|
1398
1422
|
firstIndex: currentIndexOffset,
|
|
1399
|
-
bindGroup:
|
|
1423
|
+
bindGroup: createHairBindGroup(false),
|
|
1400
1424
|
materialName: mat.name,
|
|
1401
1425
|
});
|
|
1402
1426
|
}
|
|
1403
1427
|
else if (isTransparent) {
|
|
1404
|
-
|
|
1405
|
-
type: "transparent",
|
|
1406
|
-
count: indexCount,
|
|
1407
|
-
firstIndex: currentIndexOffset,
|
|
1408
|
-
bindGroup,
|
|
1409
|
-
materialName: mat.name,
|
|
1410
|
-
});
|
|
1428
|
+
inst.drawCalls.push({ type: "transparent", count: indexCount, firstIndex: currentIndexOffset, bindGroup, materialName: mat.name });
|
|
1411
1429
|
}
|
|
1412
1430
|
else {
|
|
1413
|
-
|
|
1414
|
-
type: "opaque",
|
|
1415
|
-
count: indexCount,
|
|
1416
|
-
firstIndex: currentIndexOffset,
|
|
1417
|
-
bindGroup,
|
|
1418
|
-
materialName: mat.name,
|
|
1419
|
-
});
|
|
1431
|
+
inst.drawCalls.push({ type: "opaque", count: indexCount, firstIndex: currentIndexOffset, bindGroup, materialName: mat.name });
|
|
1420
1432
|
}
|
|
1421
1433
|
}
|
|
1422
|
-
// Edge flag is at bit 4 (0x10) in PMX format
|
|
1423
1434
|
if ((mat.edgeFlag & 0x10) !== 0 && mat.edgeSize > 0) {
|
|
1424
1435
|
const materialUniformData = new Float32Array([
|
|
1425
|
-
mat.edgeColor[0],
|
|
1426
|
-
mat.
|
|
1427
|
-
mat.edgeColor[2],
|
|
1428
|
-
mat.edgeColor[3],
|
|
1429
|
-
mat.edgeSize,
|
|
1430
|
-
0,
|
|
1431
|
-
0,
|
|
1432
|
-
0,
|
|
1436
|
+
mat.edgeColor[0], mat.edgeColor[1], mat.edgeColor[2], mat.edgeColor[3],
|
|
1437
|
+
mat.edgeSize, 0, 0, 0,
|
|
1433
1438
|
]);
|
|
1434
|
-
const
|
|
1439
|
+
const outlineUniformBuffer = this.createUniformBuffer(`${prefix}outline: ${mat.name}`, materialUniformData);
|
|
1435
1440
|
const outlineBindGroup = this.device.createBindGroup({
|
|
1436
|
-
label:
|
|
1441
|
+
label: `${prefix}outline: ${mat.name}`,
|
|
1437
1442
|
layout: this.outlineBindGroupLayout,
|
|
1438
1443
|
entries: [
|
|
1439
1444
|
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1440
|
-
{ binding: 1, resource: { buffer:
|
|
1441
|
-
{ binding: 2, resource: { buffer:
|
|
1445
|
+
{ binding: 1, resource: { buffer: outlineUniformBuffer } },
|
|
1446
|
+
{ binding: 2, resource: { buffer: inst.skinMatrixBuffer } },
|
|
1442
1447
|
],
|
|
1443
1448
|
});
|
|
1444
1449
|
if (indexCount > 0) {
|
|
1445
|
-
const outlineType = mat.isEye
|
|
1446
|
-
|
|
1447
|
-
: mat.isHair
|
|
1448
|
-
? "hair-outline"
|
|
1449
|
-
: isTransparent
|
|
1450
|
-
? "transparent-outline"
|
|
1451
|
-
: "opaque-outline";
|
|
1452
|
-
this.drawCalls.push({
|
|
1453
|
-
type: outlineType,
|
|
1454
|
-
count: indexCount,
|
|
1455
|
-
firstIndex: currentIndexOffset,
|
|
1456
|
-
bindGroup: outlineBindGroup,
|
|
1457
|
-
materialName: mat.name,
|
|
1458
|
-
});
|
|
1450
|
+
const outlineType = mat.isEye ? "eye-outline" : mat.isHair ? "hair-outline" : isTransparent ? "transparent-outline" : "opaque-outline";
|
|
1451
|
+
inst.drawCalls.push({ type: outlineType, count: indexCount, firstIndex: currentIndexOffset, bindGroup: outlineBindGroup, materialName: mat.name });
|
|
1459
1452
|
}
|
|
1460
1453
|
}
|
|
1461
1454
|
currentIndexOffset += indexCount;
|
|
1462
1455
|
}
|
|
1463
|
-
|
|
1464
|
-
for (const d of this.drawCalls) {
|
|
1456
|
+
for (const d of inst.drawCalls) {
|
|
1465
1457
|
if (d.type === "opaque" || d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes")
|
|
1466
|
-
|
|
1458
|
+
inst.shadowDrawCalls.push(d);
|
|
1467
1459
|
}
|
|
1468
1460
|
}
|
|
1469
1461
|
createMaterialUniformBuffer(label, alpha, isOverEyes, diffuseColor, ambientColor, specularColor, shininess) {
|
|
@@ -1501,8 +1493,8 @@ export class Engine {
|
|
|
1501
1493
|
this.device.queue.writeBuffer(buffer, 0, data);
|
|
1502
1494
|
return buffer;
|
|
1503
1495
|
}
|
|
1504
|
-
shouldRenderDrawCall(drawCall) {
|
|
1505
|
-
return !
|
|
1496
|
+
shouldRenderDrawCall(inst, drawCall) {
|
|
1497
|
+
return !inst.hiddenMaterials.has(drawCall.materialName);
|
|
1506
1498
|
}
|
|
1507
1499
|
async createTextureFromPath(path) {
|
|
1508
1500
|
const cached = this.textureCache.get(path);
|
|
@@ -1536,11 +1528,10 @@ export class Engine {
|
|
|
1536
1528
|
}
|
|
1537
1529
|
}
|
|
1538
1530
|
// Helper: Render eyes with stencil writing (for post-alpha-eye effect)
|
|
1539
|
-
renderEyes(pass, useReflectionPipeline = false) {
|
|
1531
|
+
renderEyes(pass, inst, useReflectionPipeline = false) {
|
|
1540
1532
|
if (useReflectionPipeline) {
|
|
1541
|
-
// For reflections, use the basic reflection pipeline instead of specialized eye pipeline
|
|
1542
1533
|
pass.setPipeline(this.reflectionPipeline);
|
|
1543
|
-
for (const draw of
|
|
1534
|
+
for (const draw of inst.drawCalls) {
|
|
1544
1535
|
if (draw.type === "eye") {
|
|
1545
1536
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1546
1537
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -1550,8 +1541,8 @@ export class Engine {
|
|
|
1550
1541
|
else {
|
|
1551
1542
|
pass.setPipeline(this.eyePipeline);
|
|
1552
1543
|
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
1553
|
-
for (const draw of
|
|
1554
|
-
if (draw.type === "eye" && this.shouldRenderDrawCall(draw)) {
|
|
1544
|
+
for (const draw of inst.drawCalls) {
|
|
1545
|
+
if (draw.type === "eye" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1555
1546
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1556
1547
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1557
1548
|
}
|
|
@@ -1559,19 +1550,15 @@ export class Engine {
|
|
|
1559
1550
|
}
|
|
1560
1551
|
}
|
|
1561
1552
|
renderGround(pass) {
|
|
1562
|
-
if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer)
|
|
1553
|
+
if (!this.groundHasReflections || !this.groundVertexBuffer || !this.groundIndexBuffer || !this.groundDrawCall)
|
|
1563
1554
|
return;
|
|
1564
1555
|
if (this.groundMode === "reflection" && this.groundReflectionTexture)
|
|
1565
1556
|
this.renderReflectionTexture();
|
|
1566
1557
|
pass.setPipeline(this.groundMode === "reflection" ? this.groundPipeline : this.groundShadowPipeline);
|
|
1567
1558
|
pass.setVertexBuffer(0, this.groundVertexBuffer);
|
|
1568
1559
|
pass.setIndexBuffer(this.groundIndexBuffer, "uint16");
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1572
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1560
|
+
pass.setBindGroup(0, this.groundDrawCall.bindGroup);
|
|
1561
|
+
pass.drawIndexed(this.groundDrawCall.count, 1, this.groundDrawCall.firstIndex, 0, 0);
|
|
1575
1562
|
}
|
|
1576
1563
|
renderReflectionTexture() {
|
|
1577
1564
|
if (!this.groundReflectionTexture)
|
|
@@ -1601,45 +1588,15 @@ export class Engine {
|
|
|
1601
1588
|
},
|
|
1602
1589
|
};
|
|
1603
1590
|
const reflectionPass = reflectionEncoder.beginRenderPass(reflectionPassDescriptor);
|
|
1604
|
-
|
|
1605
|
-
reflectionPass.setVertexBuffer(0, this.vertexBuffer);
|
|
1606
|
-
reflectionPass.setVertexBuffer(1, this.jointsBuffer);
|
|
1607
|
-
reflectionPass.setVertexBuffer(2, this.weightsBuffer);
|
|
1608
|
-
reflectionPass.setIndexBuffer(this.indexBuffer, "uint32");
|
|
1609
|
-
this.writeMirrorTransformedSkinMatrices(mirrorMatrix);
|
|
1610
|
-
reflectionPass.setPipeline(this.reflectionPipeline);
|
|
1611
|
-
for (const draw of this.drawCalls) {
|
|
1612
|
-
if (draw.type === "opaque" && this.shouldRenderDrawCall(draw)) {
|
|
1613
|
-
reflectionPass.setBindGroup(0, draw.bindGroup);
|
|
1614
|
-
reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
// Render eyes (using reflection pipeline)
|
|
1618
|
-
this.renderEyes(reflectionPass, true);
|
|
1619
|
-
// Render hair (using reflection pipeline)
|
|
1620
|
-
this.renderHair(reflectionPass, true);
|
|
1621
|
-
// Render transparent objects
|
|
1622
|
-
for (const draw of this.drawCalls) {
|
|
1623
|
-
if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
|
|
1624
|
-
reflectionPass.setBindGroup(0, draw.bindGroup);
|
|
1625
|
-
reflectionPass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
this.drawOutlines(reflectionPass, true, true);
|
|
1629
|
-
}
|
|
1591
|
+
this.forEachInstance((inst) => this.renderOneModel(reflectionPass, inst, true, mirrorMatrix));
|
|
1630
1592
|
reflectionPass.end();
|
|
1631
|
-
|
|
1632
|
-
const reflectionCommandBuffer = reflectionEncoder.finish();
|
|
1633
|
-
this.device.queue.submit([reflectionCommandBuffer]);
|
|
1634
|
-
// Restore original skin matrices
|
|
1593
|
+
this.device.queue.submit([reflectionEncoder.finish()]);
|
|
1635
1594
|
this.updateSkinMatrices();
|
|
1636
1595
|
}
|
|
1637
|
-
|
|
1638
|
-
renderHair(pass, useReflectionPipeline = false) {
|
|
1596
|
+
renderHair(pass, inst, useReflectionPipeline = false) {
|
|
1639
1597
|
if (useReflectionPipeline) {
|
|
1640
|
-
// For reflections, use the basic reflection pipeline for all hair
|
|
1641
1598
|
pass.setPipeline(this.reflectionPipeline);
|
|
1642
|
-
for (const draw of
|
|
1599
|
+
for (const draw of inst.drawCalls) {
|
|
1643
1600
|
if (draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") {
|
|
1644
1601
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1645
1602
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
@@ -1647,19 +1604,17 @@ export class Engine {
|
|
|
1647
1604
|
}
|
|
1648
1605
|
return;
|
|
1649
1606
|
}
|
|
1650
|
-
|
|
1651
|
-
const hasHair = this.drawCalls.some((d) => (d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(d));
|
|
1607
|
+
const hasHair = inst.drawCalls.some((d) => (d.type === "hair-over-eyes" || d.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(inst, d));
|
|
1652
1608
|
if (hasHair) {
|
|
1653
1609
|
pass.setPipeline(this.hairDepthPipeline);
|
|
1654
|
-
for (const draw of
|
|
1655
|
-
if ((draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(draw)) {
|
|
1610
|
+
for (const draw of inst.drawCalls) {
|
|
1611
|
+
if ((draw.type === "hair-over-eyes" || draw.type === "hair-over-non-eyes") && this.shouldRenderDrawCall(inst, draw)) {
|
|
1656
1612
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1657
1613
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1658
1614
|
}
|
|
1659
1615
|
}
|
|
1660
1616
|
}
|
|
1661
|
-
|
|
1662
|
-
const hairOverEyes = this.drawCalls.filter((d) => d.type === "hair-over-eyes" && this.shouldRenderDrawCall(d));
|
|
1617
|
+
const hairOverEyes = inst.drawCalls.filter((d) => d.type === "hair-over-eyes" && this.shouldRenderDrawCall(inst, d));
|
|
1663
1618
|
if (hairOverEyes.length > 0) {
|
|
1664
1619
|
pass.setPipeline(this.hairPipelineOverEyes);
|
|
1665
1620
|
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
@@ -1668,7 +1623,7 @@ export class Engine {
|
|
|
1668
1623
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1669
1624
|
}
|
|
1670
1625
|
}
|
|
1671
|
-
const hairOverNonEyes =
|
|
1626
|
+
const hairOverNonEyes = inst.drawCalls.filter((d) => d.type === "hair-over-non-eyes" && this.shouldRenderDrawCall(inst, d));
|
|
1672
1627
|
if (hairOverNonEyes.length > 0) {
|
|
1673
1628
|
pass.setPipeline(this.hairPipelineOverNonEyes);
|
|
1674
1629
|
pass.setStencilReference(this.STENCIL_EYE_VALUE);
|
|
@@ -1677,8 +1632,7 @@ export class Engine {
|
|
|
1677
1632
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1678
1633
|
}
|
|
1679
1634
|
}
|
|
1680
|
-
|
|
1681
|
-
const hairOutlines = this.drawCalls.filter((d) => d.type === "hair-outline" && this.shouldRenderDrawCall(d));
|
|
1635
|
+
const hairOutlines = inst.drawCalls.filter((d) => d.type === "hair-outline" && this.shouldRenderDrawCall(inst, d));
|
|
1682
1636
|
if (hairOutlines.length > 0) {
|
|
1683
1637
|
pass.setPipeline(this.hairOutlinePipeline);
|
|
1684
1638
|
for (const draw of hairOutlines) {
|
|
@@ -1688,261 +1642,203 @@ export class Engine {
|
|
|
1688
1642
|
}
|
|
1689
1643
|
}
|
|
1690
1644
|
performRaycast(screenX, screenY) {
|
|
1691
|
-
if (!this.
|
|
1645
|
+
if (!this.onRaycast || this.modelInstances.size === 0) {
|
|
1646
|
+
this.onRaycast?.("", null, screenX, screenY);
|
|
1692
1647
|
return;
|
|
1693
|
-
|
|
1694
|
-
if (materials.length === 0)
|
|
1695
|
-
return;
|
|
1696
|
-
// Get camera matrices
|
|
1648
|
+
}
|
|
1697
1649
|
const viewMatrix = this.camera.getViewMatrix();
|
|
1698
1650
|
const projectionMatrix = this.camera.getProjectionMatrix();
|
|
1699
|
-
|
|
1700
|
-
const canvas = this.canvas;
|
|
1701
|
-
const rect = canvas.getBoundingClientRect();
|
|
1702
|
-
// Convert to clip space (-1 to 1)
|
|
1651
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
1703
1652
|
const clipX = (screenX / rect.width) * 2 - 1;
|
|
1704
|
-
const clipY = 1 - (screenY / rect.height) * 2;
|
|
1705
|
-
// Create ray in clip space at near and far planes
|
|
1706
|
-
const clipNear = new Vec3(clipX, clipY, -1); // Near plane
|
|
1707
|
-
const clipFar = new Vec3(clipX, clipY, 1); // Far plane
|
|
1708
|
-
// Transform to world space using inverse view-projection matrix
|
|
1653
|
+
const clipY = 1 - (screenY / rect.height) * 2;
|
|
1709
1654
|
const viewProjMatrix = projectionMatrix.multiply(viewMatrix);
|
|
1710
1655
|
const inverseViewProj = viewProjMatrix.inverse();
|
|
1711
|
-
// Transform point through 4x4 matrix with perspective division
|
|
1712
1656
|
const transformPoint = (matrix, point) => {
|
|
1713
1657
|
const m = matrix.values;
|
|
1714
1658
|
const x = point.x, y = point.y, z = point.z;
|
|
1715
|
-
// Compute transformed point (matrix * vec4(point, 1.0))
|
|
1716
1659
|
const result = new Vec3(m[0] * x + m[4] * y + m[8] * z + m[12], m[1] * x + m[5] * y + m[9] * z + m[13], m[2] * x + m[6] * y + m[10] * z + m[14]);
|
|
1717
|
-
// Perspective division
|
|
1718
1660
|
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
1719
|
-
|
|
1720
|
-
return result.scale(invW);
|
|
1661
|
+
return result.scale(w !== 0 ? 1 / w : 1);
|
|
1721
1662
|
};
|
|
1722
|
-
const worldNear = transformPoint(inverseViewProj,
|
|
1723
|
-
const worldFar = transformPoint(inverseViewProj,
|
|
1724
|
-
// Create ray from camera position through the clicked point
|
|
1663
|
+
const worldNear = transformPoint(inverseViewProj, new Vec3(clipX, clipY, -1));
|
|
1664
|
+
const worldFar = transformPoint(inverseViewProj, new Vec3(clipX, clipY, 1));
|
|
1725
1665
|
const rayOrigin = this.camera.getPosition();
|
|
1726
1666
|
const rayDirection = worldFar.subtract(worldNear).normalize();
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const skinMatrices = this.currentModel.getSkinMatrices();
|
|
1746
|
-
// Helper function to transform point by 4x4 matrix
|
|
1747
|
-
const transformByMatrix = (matrix, offset, point) => {
|
|
1748
|
-
const m = matrix;
|
|
1749
|
-
const x = point.x, y = point.y, z = point.z;
|
|
1750
|
-
return new Vec3(m[offset + 0] * x + m[offset + 4] * y + m[offset + 8] * z + m[offset + 12], m[offset + 1] * x + m[offset + 5] * y + m[offset + 9] * z + m[offset + 13], m[offset + 2] * x + m[offset + 6] * y + m[offset + 10] * z + m[offset + 14]);
|
|
1751
|
-
};
|
|
1667
|
+
const transformByMatrix = (matrix, offset, point) => {
|
|
1668
|
+
const m = matrix, x = point.x, y = point.y, z = point.z;
|
|
1669
|
+
return new Vec3(m[offset + 0] * x + m[offset + 4] * y + m[offset + 8] * z + m[offset + 12], m[offset + 1] * x + m[offset + 5] * y + m[offset + 9] * z + m[offset + 13], m[offset + 2] * x + m[offset + 6] * y + m[offset + 10] * z + m[offset + 14]);
|
|
1670
|
+
};
|
|
1671
|
+
let closest = null;
|
|
1672
|
+
const maxDistance = 1000;
|
|
1673
|
+
this.forEachInstance((inst) => {
|
|
1674
|
+
const model = inst.model;
|
|
1675
|
+
const materials = model.getMaterials();
|
|
1676
|
+
if (materials.length === 0)
|
|
1677
|
+
return;
|
|
1678
|
+
const baseVertices = model.getVertices();
|
|
1679
|
+
const indices = model.getIndices();
|
|
1680
|
+
const skinning = model.getSkinning();
|
|
1681
|
+
if (!baseVertices?.length || !indices || !skinning)
|
|
1682
|
+
return;
|
|
1683
|
+
const vertices = new Float32Array(baseVertices.length);
|
|
1684
|
+
const skinMatrices = model.getSkinMatrices();
|
|
1752
1685
|
for (let i = 0; i < baseVertices.length; i += 8) {
|
|
1753
|
-
const vertexIndex =
|
|
1686
|
+
const vertexIndex = i / 8;
|
|
1754
1687
|
const position = new Vec3(baseVertices[i], baseVertices[i + 1], baseVertices[i + 2]);
|
|
1755
|
-
|
|
1756
|
-
const
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
skinning.joints[vertexIndex * 4 + 3],
|
|
1761
|
-
];
|
|
1762
|
-
const weights = [
|
|
1763
|
-
skinning.weights[vertexIndex * 4],
|
|
1764
|
-
skinning.weights[vertexIndex * 4 + 1],
|
|
1765
|
-
skinning.weights[vertexIndex * 4 + 2],
|
|
1766
|
-
skinning.weights[vertexIndex * 4 + 3],
|
|
1767
|
-
];
|
|
1768
|
-
// Normalize weights (same as shader)
|
|
1769
|
-
const weightSum = weights[0] + weights[1] + weights[2] + weights[3];
|
|
1770
|
-
const invWeightSum = weightSum > 0.0001 ? 1.0 / weightSum : 1.0;
|
|
1771
|
-
const normalizedWeights = weightSum > 0.0001 ? weights.map((w) => w * invWeightSum) : [1.0, 0.0, 0.0, 0.0];
|
|
1772
|
-
// Apply skinning transformation (same as shader)
|
|
1773
|
-
let skinnedPosition = new Vec3(0, 0, 0);
|
|
1688
|
+
const j0 = skinning.joints[vertexIndex * 4], j1 = skinning.joints[vertexIndex * 4 + 1], j2 = skinning.joints[vertexIndex * 4 + 2], j3 = skinning.joints[vertexIndex * 4 + 3];
|
|
1689
|
+
const w0 = skinning.weights[vertexIndex * 4] / 255, w1 = skinning.weights[vertexIndex * 4 + 1] / 255, w2 = skinning.weights[vertexIndex * 4 + 2] / 255, w3 = skinning.weights[vertexIndex * 4 + 3] / 255;
|
|
1690
|
+
const ws = w0 + w1 + w2 + w3;
|
|
1691
|
+
const nw = ws > 0.0001 ? [w0 / ws, w1 / ws, w2 / ws, w3 / ws] : [1, 0, 0, 0];
|
|
1692
|
+
let sp = new Vec3(0, 0, 0);
|
|
1774
1693
|
for (let j = 0; j < 4; j++) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
skinnedPosition = skinnedPosition.add(transformed.scale(weight));
|
|
1780
|
-
}
|
|
1694
|
+
if (nw[j] <= 0)
|
|
1695
|
+
continue;
|
|
1696
|
+
const transformed = transformByMatrix(skinMatrices, [j0, j1, j2, j3][j] * 16, position);
|
|
1697
|
+
sp = sp.add(transformed.scale(nw[j]));
|
|
1781
1698
|
}
|
|
1782
|
-
|
|
1783
|
-
vertices[i] =
|
|
1784
|
-
vertices[i +
|
|
1785
|
-
vertices[i +
|
|
1786
|
-
vertices[i +
|
|
1787
|
-
vertices[i +
|
|
1788
|
-
vertices[i +
|
|
1789
|
-
vertices[i +
|
|
1790
|
-
vertices[i + 7] = baseVertices[i + 7]; // UV Y
|
|
1699
|
+
vertices[i] = sp.x;
|
|
1700
|
+
vertices[i + 1] = sp.y;
|
|
1701
|
+
vertices[i + 2] = sp.z;
|
|
1702
|
+
vertices[i + 3] = baseVertices[i + 3];
|
|
1703
|
+
vertices[i + 4] = baseVertices[i + 4];
|
|
1704
|
+
vertices[i + 5] = baseVertices[i + 5];
|
|
1705
|
+
vertices[i + 6] = baseVertices[i + 6];
|
|
1706
|
+
vertices[i + 7] = baseVertices[i + 7];
|
|
1791
1707
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
const v1 = new Vec3(vertices[idx1], vertices[idx1 + 1], vertices[idx1 + 2]);
|
|
1806
|
-
const v2 = new Vec3(vertices[idx2], vertices[idx2 + 1], vertices[idx2 + 2]);
|
|
1807
|
-
// Find which material this triangle belongs to
|
|
1808
|
-
// Each material has mat.vertexCount indices (3 per triangle)
|
|
1809
|
-
let triangleMaterialIndex = -1;
|
|
1810
|
-
let indexOffset = 0;
|
|
1811
|
-
for (let matIdx = 0; matIdx < materials.length; matIdx++) {
|
|
1812
|
-
const mat = materials[matIdx];
|
|
1813
|
-
if (i >= indexOffset && i < indexOffset + mat.vertexCount) {
|
|
1814
|
-
triangleMaterialIndex = matIdx;
|
|
1815
|
-
break;
|
|
1708
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
1709
|
+
const idx0 = indices[i] * 8, idx1 = indices[i + 1] * 8, idx2 = indices[i + 2] * 8;
|
|
1710
|
+
const v0 = new Vec3(vertices[idx0], vertices[idx0 + 1], vertices[idx0 + 2]);
|
|
1711
|
+
const v1 = new Vec3(vertices[idx1], vertices[idx1 + 1], vertices[idx1 + 2]);
|
|
1712
|
+
const v2 = new Vec3(vertices[idx2], vertices[idx2 + 1], vertices[idx2 + 2]);
|
|
1713
|
+
let triangleMaterialIndex = -1;
|
|
1714
|
+
let indexOffset = 0;
|
|
1715
|
+
for (let matIdx = 0; matIdx < materials.length; matIdx++) {
|
|
1716
|
+
if (i >= indexOffset && i < indexOffset + materials[matIdx].vertexCount) {
|
|
1717
|
+
triangleMaterialIndex = matIdx;
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
indexOffset += materials[matIdx].vertexCount;
|
|
1816
1721
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
const f = 1.0 / a;
|
|
1832
|
-
const s = rayOrigin.subtract(v0);
|
|
1833
|
-
const u = f * s.dot(h);
|
|
1834
|
-
if (u < 0.0 || u > 1.0)
|
|
1835
|
-
continue;
|
|
1836
|
-
const q = s.cross(edge1);
|
|
1837
|
-
const v = f * rayDirection.dot(q);
|
|
1838
|
-
if (v < 0.0 || u + v > 1.0)
|
|
1839
|
-
continue;
|
|
1840
|
-
// At this point we have a hit
|
|
1841
|
-
const t = f * edge2.dot(q);
|
|
1842
|
-
if (t > 0.0001 && t < maxDistance) {
|
|
1843
|
-
// Backface culling: only consider front-facing triangles
|
|
1722
|
+
if (triangleMaterialIndex === -1)
|
|
1723
|
+
continue;
|
|
1724
|
+
const edge1 = v1.subtract(v0), edge2 = v2.subtract(v0), h = rayDirection.cross(edge2), a = edge1.dot(h);
|
|
1725
|
+
if (Math.abs(a) < 0.0001)
|
|
1726
|
+
continue;
|
|
1727
|
+
const f = 1 / a, s = rayOrigin.subtract(v0), u = f * s.dot(h);
|
|
1728
|
+
if (u < 0 || u > 1)
|
|
1729
|
+
continue;
|
|
1730
|
+
const q = s.cross(edge1), v = f * rayDirection.dot(q);
|
|
1731
|
+
if (v < 0 || u + v > 1)
|
|
1732
|
+
continue;
|
|
1733
|
+
const t = f * edge2.dot(q);
|
|
1734
|
+
if (t <= 0.0001 || t >= maxDistance)
|
|
1735
|
+
continue;
|
|
1844
1736
|
const triangleNormal = edge1.cross(edge2).normalize();
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
materialName: materials[triangleMaterialIndex].name,
|
|
1850
|
-
distance: t,
|
|
1851
|
-
};
|
|
1852
|
-
}
|
|
1737
|
+
if (triangleNormal.dot(rayDirection) >= 0)
|
|
1738
|
+
continue;
|
|
1739
|
+
if (!closest || t < closest.distance) {
|
|
1740
|
+
closest = { modelName: inst.name, materialName: materials[triangleMaterialIndex].name, distance: t };
|
|
1853
1741
|
}
|
|
1854
1742
|
}
|
|
1855
|
-
}
|
|
1856
|
-
// Call the callback with the result
|
|
1743
|
+
});
|
|
1857
1744
|
if (this.onRaycast) {
|
|
1858
|
-
|
|
1745
|
+
const hit = closest;
|
|
1746
|
+
this.onRaycast(hit?.modelName ?? "", hit?.materialName ?? null, screenX, screenY);
|
|
1859
1747
|
}
|
|
1860
1748
|
}
|
|
1861
|
-
// Render strategy: 1) Opaque non-eye/hair 2) Eyes (stencil=1) 3) Hair (depth pre-pass + split by stencil) 4) Transparent
|
|
1862
1749
|
render() {
|
|
1863
|
-
if (this.multisampleTexture
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
this.vertexBufferNeedsUpdate = true;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
// Update vertex buffer if morphs changed
|
|
1877
|
-
if (this.vertexBufferNeedsUpdate) {
|
|
1878
|
-
this.updateVertexBuffer();
|
|
1879
|
-
this.vertexBufferNeedsUpdate = false;
|
|
1880
|
-
}
|
|
1750
|
+
if (!this.multisampleTexture || !this.camera || !this.device)
|
|
1751
|
+
return;
|
|
1752
|
+
const currentTime = performance.now();
|
|
1753
|
+
const deltaTime = this.lastFrameTime > 0 ? (currentTime - this.lastFrameTime) / 1000 : 0.016;
|
|
1754
|
+
this.lastFrameTime = currentTime;
|
|
1755
|
+
this.updateCameraUniforms();
|
|
1756
|
+
this.updateRenderTarget();
|
|
1757
|
+
const hasModels = this.modelInstances.size > 0;
|
|
1758
|
+
if (hasModels) {
|
|
1759
|
+
this.updateInstances(deltaTime);
|
|
1881
1760
|
this.updateSkinMatrices();
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1761
|
+
}
|
|
1762
|
+
if (this.groundMode === "shadow")
|
|
1763
|
+
this.updateShadowLightVP();
|
|
1764
|
+
const encoder = this.device.createCommandEncoder();
|
|
1765
|
+
if (hasModels && this.groundMode === "shadow" && this.shadowMapDepthView) {
|
|
1766
|
+
const sp = encoder.beginRenderPass({
|
|
1767
|
+
colorAttachments: [],
|
|
1768
|
+
depthStencilAttachment: {
|
|
1769
|
+
view: this.shadowMapDepthView,
|
|
1770
|
+
depthClearValue: 1.0,
|
|
1771
|
+
depthLoadOp: "clear",
|
|
1772
|
+
depthStoreOp: "store",
|
|
1773
|
+
},
|
|
1774
|
+
});
|
|
1775
|
+
sp.setPipeline(this.shadowDepthPipeline);
|
|
1776
|
+
this.forEachInstance((inst) => this.drawInstanceShadow(sp, inst));
|
|
1777
|
+
sp.end();
|
|
1778
|
+
}
|
|
1779
|
+
const pass = encoder.beginRenderPass(this.renderPassDescriptor);
|
|
1780
|
+
if (hasModels)
|
|
1781
|
+
this.forEachInstance((inst) => this.renderOneModel(pass, inst, false));
|
|
1782
|
+
if (this.groundHasReflections)
|
|
1783
|
+
this.renderGround(pass);
|
|
1784
|
+
pass.end();
|
|
1785
|
+
this.device.queue.submit([encoder.finish()]);
|
|
1786
|
+
this.updateStats(performance.now() - currentTime);
|
|
1787
|
+
}
|
|
1788
|
+
drawInstanceShadow(sp, inst) {
|
|
1789
|
+
sp.setBindGroup(0, inst.shadowBindGroup);
|
|
1790
|
+
sp.setVertexBuffer(0, inst.vertexBuffer);
|
|
1791
|
+
sp.setVertexBuffer(1, inst.jointsBuffer);
|
|
1792
|
+
sp.setVertexBuffer(2, inst.weightsBuffer);
|
|
1793
|
+
sp.setIndexBuffer(inst.indexBuffer, "uint32");
|
|
1794
|
+
for (const draw of inst.shadowDrawCalls) {
|
|
1795
|
+
if (this.shouldRenderDrawCall(inst, draw))
|
|
1796
|
+
sp.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
renderOneModel(pass, inst, useReflection, mirrorMatrix) {
|
|
1800
|
+
pass.setVertexBuffer(0, inst.vertexBuffer);
|
|
1801
|
+
pass.setVertexBuffer(1, inst.jointsBuffer);
|
|
1802
|
+
pass.setVertexBuffer(2, inst.weightsBuffer);
|
|
1803
|
+
pass.setIndexBuffer(inst.indexBuffer, "uint32");
|
|
1804
|
+
if (useReflection && mirrorMatrix) {
|
|
1805
|
+
this.writeMirrorTransformedSkinMatrices(inst, mirrorMatrix);
|
|
1806
|
+
pass.setPipeline(this.reflectionPipeline);
|
|
1807
|
+
for (const draw of inst.drawCalls) {
|
|
1808
|
+
if (draw.type === "opaque" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1809
|
+
pass.setBindGroup(0, draw.bindGroup);
|
|
1810
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1907
1811
|
}
|
|
1908
|
-
sp.end();
|
|
1909
1812
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
// Pass 1: Opaque
|
|
1917
|
-
pass.setPipeline(this.modelPipeline);
|
|
1918
|
-
for (const draw of this.drawCalls) {
|
|
1919
|
-
if (draw.type === "opaque" && this.shouldRenderDrawCall(draw)) {
|
|
1920
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1921
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
// Pass 2: Eyes (writes stencil value for hair to test against)
|
|
1925
|
-
this.renderEyes(pass);
|
|
1926
|
-
this.drawOutlines(pass, false);
|
|
1927
|
-
// Pass 3: Hair rendering (depth pre-pass + shading + outlines)
|
|
1928
|
-
this.renderHair(pass);
|
|
1929
|
-
// Pass 5: Transparent
|
|
1930
|
-
pass.setPipeline(this.modelPipeline);
|
|
1931
|
-
for (const draw of this.drawCalls) {
|
|
1932
|
-
if (draw.type === "transparent" && this.shouldRenderDrawCall(draw)) {
|
|
1933
|
-
pass.setBindGroup(0, draw.bindGroup);
|
|
1934
|
-
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1935
|
-
}
|
|
1813
|
+
this.renderEyes(pass, inst, true);
|
|
1814
|
+
this.renderHair(pass, inst, true);
|
|
1815
|
+
for (const draw of inst.drawCalls) {
|
|
1816
|
+
if (draw.type === "transparent" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1817
|
+
pass.setBindGroup(0, draw.bindGroup);
|
|
1818
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1936
1819
|
}
|
|
1937
|
-
this.drawOutlines(pass, true);
|
|
1938
1820
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1821
|
+
this.drawOutlines(pass, inst, true, true);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
pass.setPipeline(this.modelPipeline);
|
|
1825
|
+
for (const draw of inst.drawCalls) {
|
|
1826
|
+
if (draw.type === "opaque" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1827
|
+
pass.setBindGroup(0, draw.bindGroup);
|
|
1828
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
this.renderEyes(pass, inst, false);
|
|
1832
|
+
this.drawOutlines(pass, inst, false);
|
|
1833
|
+
this.renderHair(pass, inst, false);
|
|
1834
|
+
pass.setPipeline(this.modelPipeline);
|
|
1835
|
+
for (const draw of inst.drawCalls) {
|
|
1836
|
+
if (draw.type === "transparent" && this.shouldRenderDrawCall(inst, draw)) {
|
|
1837
|
+
pass.setBindGroup(0, draw.bindGroup);
|
|
1838
|
+
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1941
1839
|
}
|
|
1942
|
-
pass.end();
|
|
1943
|
-
this.device.queue.submit([encoder.finish()]);
|
|
1944
|
-
this.updateStats(performance.now() - currentTime);
|
|
1945
1840
|
}
|
|
1841
|
+
this.drawOutlines(pass, inst, true);
|
|
1946
1842
|
}
|
|
1947
1843
|
updateCameraUniforms() {
|
|
1948
1844
|
const viewMatrix = this.camera.getViewMatrix();
|
|
@@ -1966,22 +1862,18 @@ export class Engine {
|
|
|
1966
1862
|
}
|
|
1967
1863
|
}
|
|
1968
1864
|
updateSkinMatrices() {
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
// Increment version to invalidate cached skinned vertices
|
|
1974
|
-
this.skinMatricesVersion++;
|
|
1865
|
+
this.forEachInstance((inst) => {
|
|
1866
|
+
const skinMatrices = inst.model.getSkinMatrices();
|
|
1867
|
+
this.device.queue.writeBuffer(inst.skinMatrixBuffer, 0, skinMatrices.buffer, skinMatrices.byteOffset, skinMatrices.byteLength);
|
|
1868
|
+
});
|
|
1975
1869
|
}
|
|
1976
|
-
drawOutlines(pass, transparent, useReflectionPipeline = false) {
|
|
1977
|
-
if (useReflectionPipeline)
|
|
1978
|
-
// Skip outlines for reflections - not critical for the effect
|
|
1870
|
+
drawOutlines(pass, inst, transparent, useReflectionPipeline = false) {
|
|
1871
|
+
if (useReflectionPipeline)
|
|
1979
1872
|
return;
|
|
1980
|
-
}
|
|
1981
1873
|
pass.setPipeline(this.outlinePipeline);
|
|
1982
1874
|
const outlineType = transparent ? "transparent-outline" : "opaque-outline";
|
|
1983
|
-
for (const draw of
|
|
1984
|
-
if (draw.type === outlineType && this.shouldRenderDrawCall(draw)) {
|
|
1875
|
+
for (const draw of inst.drawCalls) {
|
|
1876
|
+
if (draw.type === outlineType && this.shouldRenderDrawCall(inst, draw)) {
|
|
1985
1877
|
pass.setBindGroup(0, draw.bindGroup);
|
|
1986
1878
|
pass.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0);
|
|
1987
1879
|
}
|
|
@@ -2031,23 +1923,19 @@ export class Engine {
|
|
|
2031
1923
|
1,
|
|
2032
1924
|
]));
|
|
2033
1925
|
}
|
|
2034
|
-
writeMirrorTransformedSkinMatrices(mirrorMatrix) {
|
|
2035
|
-
|
|
2036
|
-
return;
|
|
2037
|
-
const originalMatrices = this.currentModel.getSkinMatrices();
|
|
1926
|
+
writeMirrorTransformedSkinMatrices(inst, mirrorMatrix) {
|
|
1927
|
+
const originalMatrices = inst.model.getSkinMatrices();
|
|
2038
1928
|
const transformedMatrices = new Float32Array(originalMatrices.length);
|
|
2039
1929
|
for (let i = 0; i < originalMatrices.length; i += 16) {
|
|
2040
1930
|
const boneMatrixValues = new Float32Array(16);
|
|
2041
|
-
for (let j = 0; j < 16; j++)
|
|
1931
|
+
for (let j = 0; j < 16; j++)
|
|
2042
1932
|
boneMatrixValues[j] = originalMatrices[i + j];
|
|
2043
|
-
}
|
|
2044
1933
|
const boneMatrix = new Mat4(boneMatrixValues);
|
|
2045
1934
|
const transformed = mirrorMatrix.multiply(boneMatrix);
|
|
2046
|
-
for (let j = 0; j < 16; j++)
|
|
1935
|
+
for (let j = 0; j < 16; j++)
|
|
2047
1936
|
transformedMatrices[i + j] = transformed.values[j];
|
|
2048
|
-
}
|
|
2049
1937
|
}
|
|
2050
|
-
this.device.queue.writeBuffer(
|
|
1938
|
+
this.device.queue.writeBuffer(inst.skinMatrixBuffer, 0, transformedMatrices);
|
|
2051
1939
|
}
|
|
2052
1940
|
}
|
|
2053
1941
|
Engine.instance = null;
|