reze-engine 0.9.4 → 0.10.0

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/src/engine.ts CHANGED
@@ -81,7 +81,7 @@ interface ModelInstance {
81
81
  export class Engine {
82
82
  private static instance: Engine | null = null
83
83
 
84
- public static getInstance(): Engine {
84
+ static getInstance(): Engine {
85
85
  if (!Engine.instance) {
86
86
  throw new Error("Engine not ready: create Engine, await init(), then load models via engine.loadModel().")
87
87
  }
@@ -199,7 +199,7 @@ export class Engine {
199
199
  }
200
200
 
201
201
  // Step 1: Get WebGPU device and context
202
- public async init() {
202
+ async init() {
203
203
  const adapter = await navigator.gpu?.requestAdapter()
204
204
  const device = await adapter?.requestDevice()
205
205
  if (!device) {
@@ -566,7 +566,12 @@ export class Engine {
566
566
  struct CameraUniforms { view: mat4x4f, projection: mat4x4f, viewPos: vec3f, _p: f32, };
567
567
  struct Light { direction: vec4f, color: vec4f, };
568
568
  struct LightUniforms { ambientColor: vec4f, lights: array<Light, 4>, };
569
- struct GroundShadowMat { diffuseColor: vec3f, fadeStart: f32, fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, _y: f32, };
569
+ struct GroundShadowMat {
570
+ diffuseColor: vec3f, fadeStart: f32,
571
+ fadeEnd: f32, shadowStrength: f32, pcfTexel: f32, gridSpacing: f32,
572
+ gridLineWidth: f32, gridLineOpacity: f32, noiseStrength: f32, _pad: f32,
573
+ gridLineColor: vec3f, _pad2: f32,
574
+ };
570
575
  struct LightVP { viewProj: mat4x4f, };
571
576
  @group(0) @binding(0) var<uniform> camera: CameraUniforms;
572
577
  @group(0) @binding(1) var<uniform> light: LightUniforms;
@@ -574,6 +579,32 @@ export class Engine {
574
579
  @group(0) @binding(3) var shadowSampler: sampler_comparison;
575
580
  @group(0) @binding(4) var<uniform> material: GroundShadowMat;
576
581
  @group(0) @binding(5) var<uniform> lightVP: LightVP;
582
+
583
+ // Hash-based noise for frosted/matte surface
584
+ fn hash2(p: vec2f) -> f32 {
585
+ var p3 = fract(vec3f(p.x, p.y, p.x) * 0.1031);
586
+ p3 += dot(p3, vec3f(p3.y + 33.33, p3.z + 33.33, p3.x + 33.33));
587
+ return fract((p3.x + p3.y) * p3.z);
588
+ }
589
+ fn valueNoise(p: vec2f) -> f32 {
590
+ let i = floor(p);
591
+ let f = fract(p);
592
+ let u = f * f * (3.0 - 2.0 * f);
593
+ return mix(mix(hash2(i), hash2(i + vec2f(1.0, 0.0)), u.x),
594
+ mix(hash2(i + vec2f(0.0, 1.0)), hash2(i + vec2f(1.0, 1.0)), u.x), u.y);
595
+ }
596
+ fn fbmNoise(p: vec2f) -> f32 {
597
+ var v = 0.0;
598
+ var a = 0.5;
599
+ var pp = p;
600
+ for (var i = 0; i < 4; i++) {
601
+ v += a * valueNoise(pp);
602
+ pp *= 2.0;
603
+ a *= 0.5;
604
+ }
605
+ return v;
606
+ }
607
+
577
608
  struct VO { @builtin(position) position: vec4f, @location(0) worldPos: vec3f, @location(1) normal: vec3f, };
578
609
  @vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f) -> VO {
579
610
  var o: VO; o.worldPos = position; o.normal = normal;
@@ -583,6 +614,8 @@ export class Engine {
583
614
  let n = normalize(i.normal);
584
615
  let centerDist = length(i.worldPos.xz);
585
616
  let edgeFade = 1.0 - smoothstep(0.0, 1.0, clamp((centerDist - material.fadeStart) / max(material.fadeEnd - material.fadeStart, 0.001), 0.0, 1.0));
617
+
618
+ // Shadow sampling
586
619
  let lclip = lightVP.viewProj * vec4f(i.worldPos, 1.0);
587
620
  let ndc = lclip.xyz / max(lclip.w, 1e-6);
588
621
  let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
@@ -596,10 +629,26 @@ export class Engine {
596
629
  }
597
630
  }
598
631
  vis *= 0.04;
632
+
633
+ // Frosted/matte micro-texture (磨砂)
634
+ let noiseVal = fbmNoise(i.worldPos.xz * 3.0);
635
+ let noiseTint = 1.0 + (noiseVal - 0.5) * material.noiseStrength;
636
+
637
+ // Grid lines — anti-aliased via screen-space derivatives
638
+ let gp = i.worldPos.xz / material.gridSpacing;
639
+ let gridFrac = abs(fract(gp - 0.5) - 0.5);
640
+ let gridDeriv = fwidth(gp);
641
+ let halfLine = material.gridLineWidth * 0.5;
642
+ let gridLine = 1.0 - min(
643
+ smoothstep(halfLine - gridDeriv.x, halfLine + gridDeriv.x, gridFrac.x),
644
+ smoothstep(halfLine - gridDeriv.y, halfLine + gridDeriv.y, gridFrac.y)
645
+ );
599
646
  let sun = light.ambientColor.xyz + light.lights[0].color.xyz * light.lights[0].color.w * max(dot(n, -light.lights[0].direction.xyz), 0.0);
600
647
  let dark = (1.0 - vis) * material.shadowStrength;
601
- let lit = material.diffuseColor * sun * (1.0 - dark * 0.65);
602
- return vec4f(lit * edgeFade, edgeFade);
648
+ var baseColor = material.diffuseColor * sun * (1.0 - dark * 0.65);
649
+ baseColor *= noiseTint;
650
+ let finalColor = mix(baseColor, material.gridLineColor, gridLine * material.gridLineOpacity * edgeFade);
651
+ return vec4f(finalColor * edgeFade, edgeFade);
603
652
  }
604
653
  `,
605
654
  })
@@ -698,7 +747,7 @@ export class Engine {
698
747
  // Screen-stable edgeline: extrusion ∝ camera distance (same idea as MMD viewers / babylon-mmd-style scaling)
699
748
  let camDist = max(length(camera.viewPos - worldPos), 0.25);
700
749
  let refDist = 30.0;
701
- let edgeScale = 0.032;
750
+ let edgeScale = 0.025;
702
751
  let expandedPos = worldPos + worldNormal * material.edgeSize * edgeScale * (camDist / refDist);
703
752
  output.position = camera.projection * camera.view * vec4f(expandedPos, 1.0);
704
753
  return output;
@@ -923,10 +972,10 @@ export class Engine {
923
972
  }
924
973
 
925
974
  /** Set static camera look-at / orbit center. Clears any model follow binding. */
926
- public setCameraTarget(v: Vec3): void
975
+ setCameraTarget(v: Vec3): void
927
976
  /** Bind camera orbit center to a model's bone (Souls-style follow cam). Pass null to unbind. */
928
- public setCameraTarget(model: Model | null, boneName: string, offset?: Vec3): void
929
- public setCameraTarget(modelOrVec: Model | Vec3 | null, boneName?: string, offset?: Vec3): void {
977
+ setCameraTarget(model: Model | null, boneName: string, offset?: Vec3): void
978
+ setCameraTarget(modelOrVec: Model | Vec3 | null, boneName?: string, offset?: Vec3): void {
930
979
  if (modelOrVec === null) {
931
980
  this.cameraTargetModel = null
932
981
  return
@@ -946,7 +995,7 @@ export class Engine {
946
995
  }
947
996
 
948
997
  /** Souls-style follow cam: orbit center tracks a model bone each frame. Shorthand for setCameraTarget(model, boneName, offset). */
949
- public setCameraFollow(model: Model | null, boneName?: string, offset?: Vec3): void {
998
+ setCameraFollow(model: Model | null, boneName?: string, offset?: Vec3): void {
950
999
  if (model === null) {
951
1000
  this.cameraTargetModel = null
952
1001
  return
@@ -958,12 +1007,12 @@ export class Engine {
958
1007
  this.cameraTargetOffset.z = offset?.z ?? 0
959
1008
  }
960
1009
 
961
- public getCameraDistance(): number { return this.camera.radius }
962
- public setCameraDistance(d: number): void { this.camera.radius = d }
963
- public getCameraAlpha(): number { return this.camera.alpha }
964
- public setCameraAlpha(a: number): void { this.camera.alpha = a }
965
- public getCameraBeta(): number { return this.camera.beta }
966
- public setCameraBeta(b: number): void { this.camera.beta = b }
1010
+ getCameraDistance(): number { return this.camera.radius }
1011
+ setCameraDistance(d: number): void { this.camera.radius = d }
1012
+ getCameraAlpha(): number { return this.camera.alpha }
1013
+ setCameraAlpha(a: number): void { this.camera.alpha = a }
1014
+ getCameraBeta(): number { return this.camera.beta }
1015
+ setCameraBeta(b: number): void { this.camera.beta = b }
967
1016
 
968
1017
  // Step 5: Create lighting buffers
969
1018
  private setupLighting() {
@@ -990,16 +1039,6 @@ export class Engine {
990
1039
  this.updateLightBuffer()
991
1040
  }
992
1041
 
993
- public clearLights() {
994
- this.lightCount = 0
995
- // Clear all light data by setting intensity to 0
996
- for (let i = 0; i < 4; i++) {
997
- const baseIndex = 4 + i * 8
998
- this.lightData[baseIndex + 7] = 0 // color.w / intensity
999
- }
1000
- this.updateLightBuffer()
1001
- }
1002
-
1003
1042
  private addLight(direction: Vec3, color: Vec3, intensity: number = 1.0): boolean {
1004
1043
  if (this.lightCount >= 4) return false
1005
1044
 
@@ -1019,7 +1058,7 @@ export class Engine {
1019
1058
  return true
1020
1059
  }
1021
1060
 
1022
- public addGround(options?: {
1061
+ addGround(options?: {
1023
1062
  width?: number
1024
1063
  height?: number
1025
1064
  diffuseColor?: Vec3
@@ -1027,19 +1066,29 @@ export class Engine {
1027
1066
  fadeEnd?: number
1028
1067
  shadowMapSize?: number
1029
1068
  shadowStrength?: number
1069
+ gridSpacing?: number
1070
+ gridLineWidth?: number
1071
+ gridLineOpacity?: number
1072
+ gridLineColor?: Vec3
1073
+ noiseStrength?: number
1030
1074
  }): void {
1031
1075
  const opts = {
1032
- width: 100,
1033
- height: 100,
1034
- diffuseColor: new Vec3(1, 1, 1),
1035
- fadeStart: 5.0,
1036
- fadeEnd: 60.0,
1076
+ width: 160,
1077
+ height: 160,
1078
+ diffuseColor: new Vec3(0.8, 0.1, 1.0),
1079
+ fadeStart: 10.0,
1080
+ fadeEnd: 80.0,
1037
1081
  shadowMapSize: 4096,
1038
1082
  shadowStrength: 1.0,
1083
+ gridSpacing: 4.2,
1084
+ gridLineWidth: 0.012,
1085
+ gridLineOpacity: 0.4,
1086
+ gridLineColor: new Vec3(0.85, 0.85, 0.85),
1087
+ noiseStrength: 0.05,
1039
1088
  ...options,
1040
1089
  }
1041
1090
  this.createGroundGeometry(opts.width, opts.height)
1042
- this.createShadowGroundResources(opts.shadowMapSize, opts.diffuseColor, opts.fadeStart, opts.fadeEnd, opts.shadowStrength)
1091
+ this.createShadowGroundResources(opts)
1043
1092
  this.hasGround = true
1044
1093
  this.groundDrawCall = {
1045
1094
  type: "ground",
@@ -1054,11 +1103,11 @@ export class Engine {
1054
1103
  this.device.queue.writeBuffer(this.lightUniformBuffer, 0, this.lightData)
1055
1104
  }
1056
1105
 
1057
- public getStats(): EngineStats {
1106
+ getStats(): EngineStats {
1058
1107
  return { ...this.stats }
1059
1108
  }
1060
1109
 
1061
- public runRenderLoop(callback?: () => void) {
1110
+ runRenderLoop(callback?: () => void) {
1062
1111
  this.renderLoopCallback = callback || null
1063
1112
 
1064
1113
  const loop = () => {
@@ -1074,7 +1123,7 @@ export class Engine {
1074
1123
  this.animationFrameId = requestAnimationFrame(loop)
1075
1124
  }
1076
1125
 
1077
- public stopRenderLoop() {
1126
+ stopRenderLoop() {
1078
1127
  if (this.animationFrameId !== null) {
1079
1128
  cancelAnimationFrame(this.animationFrameId)
1080
1129
  this.animationFrameId = null
@@ -1082,7 +1131,7 @@ export class Engine {
1082
1131
  this.renderLoopCallback = null
1083
1132
  }
1084
1133
 
1085
- public dispose() {
1134
+ dispose() {
1086
1135
  this.stopRenderLoop()
1087
1136
  this.forEachInstance((inst) => inst.model.stopAnimation())
1088
1137
  if (Engine.instance === this) Engine.instance = null
@@ -1100,9 +1149,9 @@ export class Engine {
1100
1149
  }
1101
1150
  }
1102
1151
 
1103
- public async loadModel(path: string): Promise<Model>
1104
- public async loadModel(name: string, path: string): Promise<Model>
1105
- public async loadModel(nameOrPath: string, path?: string): Promise<Model> {
1152
+ async loadModel(path: string): Promise<Model>
1153
+ async loadModel(name: string, path: string): Promise<Model>
1154
+ async loadModel(nameOrPath: string, path?: string): Promise<Model> {
1106
1155
  const pmxPath = path === undefined ? nameOrPath : path
1107
1156
  const name = path === undefined ? "model_" + (this._nextDefaultModelId++) : nameOrPath
1108
1157
  const model = await PmxLoader.load(pmxPath)
@@ -1111,7 +1160,7 @@ export class Engine {
1111
1160
  return model
1112
1161
  }
1113
1162
 
1114
- public async addModel(model: Model, pmxPath: string, name?: string): Promise<string> {
1163
+ async addModel(model: Model, pmxPath: string, name?: string): Promise<string> {
1115
1164
  const requested = name ?? model.name
1116
1165
  let key = requested
1117
1166
  let n = 1
@@ -1125,19 +1174,19 @@ export class Engine {
1125
1174
  return key
1126
1175
  }
1127
1176
 
1128
- public removeModel(name: string): void {
1177
+ removeModel(name: string): void {
1129
1178
  this.modelInstances.delete(name)
1130
1179
  }
1131
1180
 
1132
- public getModelNames(): string[] {
1181
+ getModelNames(): string[] {
1133
1182
  return Array.from(this.modelInstances.keys())
1134
1183
  }
1135
1184
 
1136
- public getModel(name: string): Model | null {
1185
+ getModel(name: string): Model | null {
1137
1186
  return this.modelInstances.get(name)?.model ?? null
1138
1187
  }
1139
1188
 
1140
- public markVertexBufferDirty(modelNameOrModel?: string | Model): void {
1189
+ markVertexBufferDirty(modelNameOrModel?: string | Model): void {
1141
1190
  if (modelNameOrModel === undefined) return
1142
1191
  if (typeof modelNameOrModel === "string") {
1143
1192
  const inst = this.modelInstances.get(modelNameOrModel)
@@ -1152,38 +1201,38 @@ export class Engine {
1152
1201
  }
1153
1202
  }
1154
1203
 
1155
- public setMaterialVisible(modelName: string, materialName: string, visible: boolean): void {
1204
+ setMaterialVisible(modelName: string, materialName: string, visible: boolean): void {
1156
1205
  const inst = this.modelInstances.get(modelName)
1157
1206
  if (!inst) return
1158
1207
  if (visible) inst.hiddenMaterials.delete(materialName)
1159
1208
  else inst.hiddenMaterials.add(materialName)
1160
1209
  }
1161
1210
 
1162
- public toggleMaterialVisible(modelName: string, materialName: string): void {
1211
+ toggleMaterialVisible(modelName: string, materialName: string): void {
1163
1212
  const inst = this.modelInstances.get(modelName)
1164
1213
  if (!inst) return
1165
1214
  if (inst.hiddenMaterials.has(materialName)) inst.hiddenMaterials.delete(materialName)
1166
1215
  else inst.hiddenMaterials.add(materialName)
1167
1216
  }
1168
1217
 
1169
- public isMaterialVisible(modelName: string, materialName: string): boolean {
1218
+ isMaterialVisible(modelName: string, materialName: string): boolean {
1170
1219
  const inst = this.modelInstances.get(modelName)
1171
1220
  return inst ? !inst.hiddenMaterials.has(materialName) : false
1172
1221
  }
1173
1222
 
1174
- public setIKEnabled(enabled: boolean): void {
1223
+ setIKEnabled(enabled: boolean): void {
1175
1224
  this.ikEnabled = enabled
1176
1225
  }
1177
1226
 
1178
- public getIKEnabled(): boolean {
1227
+ getIKEnabled(): boolean {
1179
1228
  return this.ikEnabled
1180
1229
  }
1181
1230
 
1182
- public setPhysicsEnabled(enabled: boolean): void {
1231
+ setPhysicsEnabled(enabled: boolean): void {
1183
1232
  this.physicsEnabled = enabled
1184
1233
  }
1185
1234
 
1186
- public getPhysicsEnabled(): boolean {
1235
+ getPhysicsEnabled(): boolean {
1187
1236
  return this.physicsEnabled
1188
1237
  }
1189
1238
 
@@ -1391,13 +1440,19 @@ export class Engine {
1391
1440
  this.device.queue.writeBuffer(this.groundIndexBuffer, 0, indices)
1392
1441
  }
1393
1442
 
1394
- private createShadowGroundResources(
1395
- shadowMapSize: number,
1396
- diffuseColor: Vec3,
1397
- fadeStart: number,
1398
- fadeEnd: number,
1443
+ private createShadowGroundResources(opts: {
1444
+ shadowMapSize: number
1445
+ diffuseColor: Vec3
1446
+ fadeStart: number
1447
+ fadeEnd: number
1399
1448
  shadowStrength: number
1400
- ) {
1449
+ gridSpacing: number
1450
+ gridLineWidth: number
1451
+ gridLineOpacity: number
1452
+ gridLineColor: Vec3
1453
+ noiseStrength: number
1454
+ }) {
1455
+ const { shadowMapSize, diffuseColor, fadeStart, fadeEnd, shadowStrength, gridSpacing, gridLineWidth, gridLineOpacity, gridLineColor, noiseStrength } = opts
1401
1456
  this.shadowMapTexture = this.device.createTexture({
1402
1457
  label: "shadow map",
1403
1458
  size: [shadowMapSize, shadowMapSize],
@@ -1405,16 +1460,13 @@ export class Engine {
1405
1460
  usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
1406
1461
  })
1407
1462
  this.shadowMapDepthView = this.shadowMapTexture.createView()
1408
- const gb = new Float32Array(8)
1409
- gb[0] = diffuseColor.x
1410
- gb[1] = diffuseColor.y
1411
- gb[2] = diffuseColor.z
1412
- gb[3] = fadeStart
1413
- gb[4] = fadeEnd
1414
- gb[5] = shadowStrength
1415
- gb[6] = 1 / shadowMapSize
1416
- gb[7] = 0
1417
- this.groundShadowMaterialBuffer = this.device.createBuffer({ size: 64, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST })
1463
+ // Layout: diffuseColor(3f) fadeStart(1f) | fadeEnd(1f) shadowStrength(1f) pcfTexel(1f) gridSpacing(1f) | gridLineWidth(1f) gridOpacity(1f) noiseStrength(1f) _pad(1f) | gridColor(3f) _pad2(1f)
1464
+ const gb = new Float32Array(16)
1465
+ gb[0] = diffuseColor.x; gb[1] = diffuseColor.y; gb[2] = diffuseColor.z; gb[3] = fadeStart
1466
+ gb[4] = fadeEnd; gb[5] = shadowStrength; gb[6] = 1 / shadowMapSize; gb[7] = gridSpacing
1467
+ gb[8] = gridLineWidth; gb[9] = gridLineOpacity; gb[10] = noiseStrength; gb[11] = 0
1468
+ gb[12] = gridLineColor.x; gb[13] = gridLineColor.y; gb[14] = gridLineColor.z; gb[15] = 0
1469
+ this.groundShadowMaterialBuffer = this.device.createBuffer({ size: gb.byteLength, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST })
1418
1470
  this.device.queue.writeBuffer(this.groundShadowMaterialBuffer, 0, gb)
1419
1471
  this.groundShadowBindGroup = this.device.createBindGroup({
1420
1472
  label: "ground shadow bind",
@@ -1741,7 +1793,7 @@ export class Engine {
1741
1793
  this.onRaycast(hitModel, hitMaterial, screenX, screenY)
1742
1794
  }
1743
1795
 
1744
- public render() {
1796
+ render() {
1745
1797
  if (!this.multisampleTexture || !this.camera || !this.device) return
1746
1798
 
1747
1799
  const currentTime = performance.now()
package/src/ik-solver.ts CHANGED
@@ -21,13 +21,13 @@ const enum InternalSolveAxis {
21
21
  }
22
22
 
23
23
  class IKChain {
24
- public readonly boneIndex: number
25
- public readonly minimumAngle: Vec3 | null
26
- public readonly maximumAngle: Vec3 | null
27
- public readonly rotationOrder: InternalEulerRotationOrder
28
- public readonly solveAxis: InternalSolveAxis
24
+ readonly boneIndex: number
25
+ readonly minimumAngle: Vec3 | null
26
+ readonly maximumAngle: Vec3 | null
27
+ readonly rotationOrder: InternalEulerRotationOrder
28
+ readonly solveAxis: InternalSolveAxis
29
29
 
30
- public constructor(boneIndex: number, link: IKLink) {
30
+ constructor(boneIndex: number, link: IKLink) {
31
31
  this.boneIndex = boneIndex
32
32
 
33
33
  if (link.hasLimit && link.minAngle && link.maxAngle) {
@@ -76,7 +76,7 @@ export class IKSolverSystem {
76
76
  private static readonly EPSILON = 1.0e-8
77
77
  private static readonly THRESHOLD = (88 * Math.PI) / 180
78
78
 
79
- public static solve(
79
+ static solve(
80
80
  ikSolvers: IKSolver[],
81
81
  bones: Bone[],
82
82
  localRotations: Quat[],
package/src/index.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  export { Engine, type EngineStats } from "./engine"
2
2
  export { Model } from "./model"
3
3
  export { Vec3, Quat, Mat4 } from "./math"
4
- export {
5
- AnimationState,
6
- type AnimationClip,
7
- type BoneKeyframe,
8
- type MorphKeyframe,
4
+ export type {
5
+ AnimationClip,
6
+ AnimationPlayOptions,
7
+ AnimationProgress,
8
+ BoneKeyframe,
9
+ MorphKeyframe,
10
+ BoneInterpolation,
11
+ ControlPoint,
9
12
  } from "./animation"
13
+ export { FPS } from "./animation"
10
14
  export { Physics, type PhysicsOptions } from "./physics"