From 4b346855abb622803ca216d90ced4b9f5b08221c Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Mon, 22 Jul 2024 13:31:38 +0800 Subject: [PATCH 01/25] chore: update ClearCoat sample --- samples/material/Sample_ClearCoat.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/material/Sample_ClearCoat.ts b/samples/material/Sample_ClearCoat.ts index de889f41..a66d003b 100644 --- a/samples/material/Sample_ClearCoat.ts +++ b/samples/material/Sample_ClearCoat.ts @@ -84,9 +84,8 @@ class Sample_ClearCoat { let mat = new LitMaterial(); mat.baseColor = Color.randomRGB(); mat.metallic = 1; - mat.roughness = 0.85; - // mat.clearCoatRoughnessMap = clearCoatRoughnessTex; - // mat.clearcoatFactor = i / 10; + mat.roughness = (10 - i) / 30; + mat.clearCoatRoughnessMap = clearCoatRoughnessTex; mat.clearcoatColor = new Color(1.0, 1.0, 1.0); mat.clearcoatWeight = 1; mat.clearcoatFactor = 0.5; From af74bb1c14a1ee42af749868271f9b45a65c2384 Mon Sep 17 00:00:00 2001 From: Codeboy Date: Tue, 23 Jul 2024 18:16:28 +0800 Subject: [PATCH 02/25] fix(GUI): add option to receive post effects (#426) --- public | 2 +- samples/gui/Sample_POI.ts | 27 ++++- src/components/gui/core/GUIMaterial.ts | 42 ++++--- src/components/gui/core/GUIShader.ts | 10 +- src/components/gui/uiComponents/UIPanel.ts | 4 + src/components/renderer/RenderNode.ts | 2 + .../descriptor/WebGPUDescriptorCreator.ts | 9 +- src/gfx/renderJob/frame/GBufferFrame.ts | 26 +++-- src/gfx/renderJob/jobs/ForwardRenderJob.ts | 11 +- src/gfx/renderJob/jobs/RendererJob.ts | 13 ++- .../renderJob/passRenderer/RenderContext.ts | 6 + .../passRenderer/color/ColorPassRenderer.ts | 8 +- .../passRenderer/color/GUIPassRenderer.ts | 109 ++++++++++++++++++ .../passRenderer/post/PostRenderer.ts | 16 +-- .../renderJob/passRenderer/state/PassType.ts | 2 +- src/index.ts | 1 + 16 files changed, 233 insertions(+), 55 deletions(-) create mode 100644 src/gfx/renderJob/passRenderer/color/GUIPassRenderer.ts diff --git a/public b/public index eeeaca67..5a6f7231 160000 --- a/public +++ b/public @@ -1 +1 @@ -Subproject commit eeeaca67f9ada0a09cc9d6fb93d3fe449bf4ff69 +Subproject commit 5a6f72318f7eac3cacde980adc3a3a108c2b0035 diff --git a/samples/gui/Sample_POI.ts b/samples/gui/Sample_POI.ts index 9ab45392..57c3d4f6 100644 --- a/samples/gui/Sample_POI.ts +++ b/samples/gui/Sample_POI.ts @@ -1,11 +1,16 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { createExampleScene, createSceneParam } from "@samples/utils/ExampleScene"; -import { Scene3D, PropertyAnimation, Engine3D, Object3D, Object3DUtil, PropertyAnimClip, WrapMode, WorldPanel, BillboardType, TextAnchor, UIImage, UIShadow, UITextField, Vector3, Color, Time } from "@orillusion/core"; +import { + Scene3D, PropertyAnimation, Engine3D, Object3D, Object3DUtil, PropertyAnimClip, + WrapMode, WorldPanel, BillboardType, TextAnchor, UIImage, UIShadow, UITextField, + Vector3, Color, Time, PostProcessingComponent, BloomPost, UIPanel +} from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; class Sample_POI { scene: Scene3D; panel: WorldPanel; + panel2: UIPanel; position: Vector3; async run() { @@ -18,19 +23,26 @@ class Sample_POI { await Engine3D.init({ renderLoop: () => { this.loop(); } }); let param = createSceneParam(); + param.light.intensity = 5; param.camera.distance = 30; let exampleScene = createExampleScene(param); GUIHelp.init(); this.scene = exampleScene.scene; - // exampleScene.camera.enableCSM = true; Engine3D.startRenderView(exampleScene.view); + let postCom = this.scene.addComponent(PostProcessingComponent); + let bloom = postCom.addPost(BloomPost); + + bloom.luminanceThreshole = 1; + bloom.bloomIntensity = 0.8; await this.initScene(); this.initDuckPOI(); this.initScenePOI(); + this.panel2.renderer.isRecievePostEffectUI = true; + GUIHelp.add(this.panel2.renderer, 'isRecievePostEffectUI'); } private modelContainer: Object3D; @@ -73,7 +85,7 @@ class Sample_POI { return animation; } - private initDuckPOI() { + private initDuckPOI(): UIPanel { let canvas = this.scene.view.enableUICanvas(); //panel this.panel = new Object3D().addComponent(WorldPanel); @@ -99,14 +111,16 @@ class Sample_POI { text.fontSize = 4; text.color = new Color(0, 0, 0, 1); text.alignment = TextAnchor.MiddleCenter; - GUIUtil.renderUIPanel(this.panel, true); + GUIUtil.renderUIPanel(this.panel, false); + return this.panel; } private sceneText: UITextField; - private initScenePOI() { + private initScenePOI(): UIPanel { let canvas = this.scene.view.enableUICanvas(); //panel let panel = new Object3D().addComponent(WorldPanel); + this.panel2 = panel; panel.cullMode = "none"; //add to canvas canvas.addChild(panel.object3D); @@ -123,11 +137,12 @@ class Sample_POI { text.uiTransform.resize(80, 16); text.text = this.title; text.fontSize = 10; - text.color = new Color(0.5, 1.0, 0.5, 1.0); + text.color = new Color(0.5, 1.5, 0.5, 1.0); text.alignment = TextAnchor.MiddleLeft; panelRoot.addComponent(UIShadow).shadowOffset.multiplyScaler(0.2); this.sceneText = text; + return panel; } private charCount = 0; diff --git a/src/components/gui/core/GUIMaterial.ts b/src/components/gui/core/GUIMaterial.ts index 7f6f3ad0..21c35592 100644 --- a/src/components/gui/core/GUIMaterial.ts +++ b/src/components/gui/core/GUIMaterial.ts @@ -29,32 +29,38 @@ export class GUIMaterial extends Material { let newShader = new Shader(); + // colorPass.transparent = true; + // colorPass.receiveEnv = false; + + this.addColorPass(newShader, PassType.COLOR, space); + this.addColorPass(newShader, PassType.UI, space); + this.shader = newShader; + } + + private addColorPass(shader: Shader, passType: PassType, space: GUISpace) { let shaderKey: string = space == GUISpace.View ? 'GUI_shader_view' : 'GUI_shader_world'; - let colorPass = new RenderShaderPass(shaderKey, shaderKey); - colorPass.passType = PassType.COLOR; - colorPass.setShaderEntry(`VertMain`, `FragMain`); + let shaderPass = new RenderShaderPass(shaderKey, shaderKey); + shaderPass.passType = passType; + shaderPass.setShaderEntry(`VertMain`, `FragMain`); - colorPass.setUniformVector4('scissorRect', new Vector4()); - colorPass.setUniformVector2('screenSize', this._screenSize); - colorPass.setUniformFloat('scissorCornerRadius', 0.0); - colorPass.setUniformFloat('scissorFadeOutSize', 0.0); + shaderPass.setUniformVector4('scissorRect', new Vector4()); + shaderPass.setUniformVector2('screenSize', this._screenSize); + shaderPass.setUniformFloat('scissorCornerRadius', 0.0); + shaderPass.setUniformFloat('scissorFadeOutSize', 0.0); - colorPass.setUniformFloat('pixelRatio', 1); - colorPass.setUniformVector3('v3', Vector3.ZERO); + shaderPass.setUniformFloat('pixelRatio', 1); + shaderPass.setUniformVector3('v3', Vector3.ZERO); - let shaderState = colorPass.shaderState; + let shaderState = shaderPass.shaderState; // shaderState.useZ = false; shaderState.depthWriteEnabled = false; - colorPass.blendMode = BlendMode.ALPHA; - colorPass.depthCompare = space == GUISpace.View ? GPUCompareFunction.always : GPUCompareFunction.less_equal; - colorPass.cullMode = GPUCullMode.back; - newShader.addRenderPass(colorPass); - // colorPass.transparent = true; - // colorPass.receiveEnv = false; - - this.shader = newShader; + shaderPass.blendMode = BlendMode.NORMAL; + shaderPass.depthCompare = space == GUISpace.View ? GPUCompareFunction.always : GPUCompareFunction.less_equal; + shaderPass.cullMode = GPUCullMode.back; + shader.addRenderPass(shaderPass); } + public setPanelRatio(pixelRatio: number) { this.shader.setUniformFloat('pixelRatio', pixelRatio); } diff --git a/src/components/gui/core/GUIShader.ts b/src/components/gui/core/GUIShader.ts index a65d755b..156d32b3 100644 --- a/src/components/gui/core/GUIShader.ts +++ b/src/components/gui/core/GUIShader.ts @@ -150,14 +150,18 @@ export class GUIShader { }else if(texId == 6){ ${this.sampleTexture(6)} } - color *= vColor4; - color.a *= scissorAlpha; + var rgb = color.rgb; + var alpha = color.a; + + rgb *= vColor4.rgb; + alpha *= vColor4.a; + alpha *= scissorAlpha; if(color.a < EPSILON) { discard; } - fragmentOutput.color = color; + fragmentOutput.color = vec4(rgb, alpha); return fragmentOutput ; }`; diff --git a/src/components/gui/uiComponents/UIPanel.ts b/src/components/gui/uiComponents/UIPanel.ts index 44a7d613..48fa05b0 100644 --- a/src/components/gui/uiComponents/UIPanel.ts +++ b/src/components/gui/uiComponents/UIPanel.ts @@ -93,6 +93,10 @@ export class UIPanel extends UIImage { return this._maxCount; } + public get renderer() { + return this._uiRenderer; + } + public set billboard(type: BillboardType) { if (this.space == GUISpace.View) { type = BillboardType.None; diff --git a/src/components/renderer/RenderNode.ts b/src/components/renderer/RenderNode.ts index 7985d991..5df939fa 100644 --- a/src/components/renderer/RenderNode.ts +++ b/src/components/renderer/RenderNode.ts @@ -54,6 +54,7 @@ export class RenderNode extends ComponentBase { protected _passInit: Map = new Map(); public isRenderOrderChange?: boolean; public needSortOnCameraZ?: boolean; + public isRecievePostEffectUI?: boolean; protected _octreeBinder: { octree: Octree, entity: OctreeEntity }; /** @@ -102,6 +103,7 @@ export class RenderNode extends ComponentBase { this.castShadow = from.castShadow; this.castGI = from.castGI; this.rendererMask = from.rendererMask; + this.isRecievePostEffectUI = from.isRecievePostEffectUI; return this; } diff --git a/src/gfx/graphics/webGpu/descriptor/WebGPUDescriptorCreator.ts b/src/gfx/graphics/webGpu/descriptor/WebGPUDescriptorCreator.ts index bdac0b25..34f66e80 100644 --- a/src/gfx/graphics/webGpu/descriptor/WebGPUDescriptorCreator.ts +++ b/src/gfx/graphics/webGpu/descriptor/WebGPUDescriptorCreator.ts @@ -4,6 +4,7 @@ import { GPUTextureFormat } from '../WebGPUConst'; import { webGPUContext } from '../Context3D'; import { RendererPassState } from '../../../renderJob/passRenderer/state/RendererPassState'; import { CResizeEvent } from '../../../../event/CResizeEvent'; +import { GBufferFrame } from '../../../renderJob/frame/GBufferFrame'; /** * @internal * @author sirxu @@ -30,8 +31,14 @@ export class WebGPUDescriptorCreator { if (rtFrame && rtFrame.renderTargets.length > 0) { rps.renderTargets = rtFrame.renderTargets; rps.rtTextureDescriptors = rtFrame.rtDescriptors; - rps.renderPassDescriptor = WebGPUDescriptorCreator.getRenderPassDescriptor(rps); + if (rps.renderPassDescriptor.depthStencilAttachment) { + rps.renderPassDescriptor.depthStencilAttachment.depthLoadOp = rtFrame.depthLoadOp; + } + if (loadOp == 'load' && rtFrame?.renderTargets[0] && rtFrame.renderTargets[0].name.startsWith(GBufferFrame.gui_GBuffer)) { + rps.renderPassDescriptor.colorAttachments[0].loadOp = 'load' + } + rps.depthLoadOp = rtFrame.depthLoadOp; rps.renderBundleEncoderDescriptor = WebGPUDescriptorCreator.getRenderBundleDescriptor(rps); rps.renderTargetTextures = []; for (let i = 0; i < rtFrame.renderTargets.length; i++) { diff --git a/src/gfx/renderJob/frame/GBufferFrame.ts b/src/gfx/renderJob/frame/GBufferFrame.ts index f32be552..bc8f1fa8 100644 --- a/src/gfx/renderJob/frame/GBufferFrame.ts +++ b/src/gfx/renderJob/frame/GBufferFrame.ts @@ -10,6 +10,7 @@ import { RTResourceMap } from "./RTResourceMap"; export class GBufferFrame extends RTFrame { public static colorPass_GBuffer: string = "ColorPassGBuffer"; public static reflections_GBuffer: string = "reflections_GBuffer"; + public static gui_GBuffer: string = "gui_GBuffer"; public static gBufferMap: Map = new Map(); // public static bufferTexture: boolean = false; @@ -20,7 +21,7 @@ export class GBufferFrame extends RTFrame { super([], []); } - createGBuffer(key: string, rtWidth: number, rtHeight: number, autoResize: boolean = true, outColor: boolean = true) { + createGBuffer(key: string, rtWidth: number, rtHeight: number, autoResize: boolean = true, outColor: boolean = true, depthTexture?: RenderTexture) { let attachments = this.renderTargets; let reDescriptors = this.rtDescriptors; if (outColor) { @@ -34,11 +35,12 @@ export class GBufferFrame extends RTFrame { this._compressGBufferTex = new RenderTexture(rtWidth, rtHeight, GPUTextureFormat.rgba32float, false, undefined, 1, 0, true, true); attachments.push(this._compressGBufferTex); - this.depthTexture = new RenderTexture(rtWidth, rtHeight, GPUTextureFormat.depth24plus, false, undefined, 1, 0, true, true); - this.depthTexture.name = key + `_depthTexture`; - - let depthDec = new RTDescriptor(); - depthDec.loadOp = `load`; + if (depthTexture) { + this.depthTexture = depthTexture; + } else { + this.depthTexture = new RenderTexture(rtWidth, rtHeight, GPUTextureFormat.depth24plus, false, undefined, 1, 0, true, true); + this.depthTexture.name = key + `_depthTexture`; + } let compressGBufferRTDes: RTDescriptor; compressGBufferRTDes = new RTDescriptor(); @@ -65,7 +67,7 @@ export class GBufferFrame extends RTFrame { /** * @internal */ - public static getGBufferFrame(key: string, fixedWidth: number = 0, fixedHeight: number = 0, outColor: boolean = true): GBufferFrame { + public static getGBufferFrame(key: string, fixedWidth: number = 0, fixedHeight: number = 0, outColor: boolean = true, depthTexture?: RenderTexture): GBufferFrame { let gBuffer: GBufferFrame; if (!GBufferFrame.gBufferMap.has(key)) { gBuffer = new GBufferFrame(); @@ -76,7 +78,8 @@ export class GBufferFrame extends RTFrame { fixedWidth == 0 ? size[0] : fixedWidth, fixedHeight == 0 ? size[1] : fixedHeight, fixedWidth != 0 && fixedHeight != 0, - outColor + outColor, + depthTexture ); GBufferFrame.gBufferMap.set(key, gBuffer); } else { @@ -85,6 +88,13 @@ export class GBufferFrame extends RTFrame { return gBuffer; } + + public static getGUIBufferFrame() { + let colorRTFrame = this.getGBufferFrame(this.colorPass_GBuffer); + let rtFrame = GBufferFrame.getGBufferFrame(GBufferFrame.gui_GBuffer, 0, 0, true, colorRTFrame.depthTexture); + return rtFrame; + } + public clone() { let gBufferFrame = new GBufferFrame(); this.clone2Frame(gBufferFrame); diff --git a/src/gfx/renderJob/jobs/ForwardRenderJob.ts b/src/gfx/renderJob/jobs/ForwardRenderJob.ts index c1b77590..d4021960 100644 --- a/src/gfx/renderJob/jobs/ForwardRenderJob.ts +++ b/src/gfx/renderJob/jobs/ForwardRenderJob.ts @@ -9,6 +9,7 @@ import { webGPUContext } from '../../graphics/webGpu/Context3D'; import { RTResourceConfig } from '../config/RTResourceConfig'; import { RTResourceMap } from '../frame/RTResourceMap'; import { GPUTextureFormat } from '../../graphics/webGpu/WebGPUConst'; +import { GUIPassRenderer } from '../passRenderer/color/GUIPassRenderer'; /** * Forward+ * Every time a forward rendering is performed, @@ -25,10 +26,9 @@ export class ForwardRenderJob extends RendererJob { public start(): void { super.start(); - - let rtFrame = GBufferFrame.getGBufferFrame("ColorPassGBuffer"); { let colorPassRenderer = new ColorPassRenderer(); + let rtFrame = GBufferFrame.getGBufferFrame(GBufferFrame.colorPass_GBuffer); if (Engine3D.setting.render.zPrePass) { rtFrame.zPreTexture = this.depthPassRenderer.rendererPassState.depthTexture; @@ -50,6 +50,13 @@ export class ForwardRenderJob extends RendererJob { this.rendererMap.addRenderer(colorPassRenderer); } + { + let guiFrame = GBufferFrame.getGUIBufferFrame(); + let guiPassRenderer = new GUIPassRenderer(); + guiPassRenderer.setRenderStates(guiFrame); + this.rendererMap.addRenderer(guiPassRenderer); + } + if (Engine3D.setting.render.debug) { this.debug(); } diff --git a/src/gfx/renderJob/jobs/RendererJob.ts b/src/gfx/renderJob/jobs/RendererJob.ts index d45fb2ab..762b7d47 100644 --- a/src/gfx/renderJob/jobs/RendererJob.ts +++ b/src/gfx/renderJob/jobs/RendererJob.ts @@ -231,6 +231,7 @@ export class RendererJob { this.ddgiProbeRenderer.render(view, this.occlusionSystem); } + let passList = this.rendererMap.getAllPassRenderer(); for (let i = 0; i < passList.length; i++) { const renderer = passList[i]; @@ -238,10 +239,16 @@ export class RendererJob { renderer.render(view, this.occlusionSystem, this.clusterLightingRender.clusterLightingBuffer, false); } - if (this.postRenderer && this.postRenderer.postList.size > 0) { - this.postRenderer.render(view); - } + this.postRenderer.render(view); + + //GUI + let guiRenderer = this.rendererMap.getRenderer(PassType.UI); + guiRenderer.compute(view, this.occlusionSystem); + guiRenderer.render(view, this.occlusionSystem, this.clusterLightingRender.clusterLightingBuffer, false); + //output + let lastTexture = GBufferFrame.getGUIBufferFrame().getColorTexture(); + this.postRenderer.presentContent(view, lastTexture); } public debug() { diff --git a/src/gfx/renderJob/passRenderer/RenderContext.ts b/src/gfx/renderJob/passRenderer/RenderContext.ts index 4d7c698c..560d4388 100644 --- a/src/gfx/renderJob/passRenderer/RenderContext.ts +++ b/src/gfx/renderJob/passRenderer/RenderContext.ts @@ -58,6 +58,12 @@ export class RenderContext { this.beginNewEncoder(); } + public specialtRenderPass() { + this.beginContinueRendererPassState('load', 'load'); + this.begineNewCommand(); + this.beginNewEncoder(); + } + public endRenderPass() { this.endEncoder(); this.endCommand(); diff --git a/src/gfx/renderJob/passRenderer/color/ColorPassRenderer.ts b/src/gfx/renderJob/passRenderer/color/ColorPassRenderer.ts index 12090064..a3cf2fed 100644 --- a/src/gfx/renderJob/passRenderer/color/ColorPassRenderer.ts +++ b/src/gfx/renderJob/passRenderer/color/ColorPassRenderer.ts @@ -1,17 +1,16 @@ -import { GlobalBindGroup } from "../../../.."; import { Engine3D } from "../../../../Engine3D"; import { RenderNode } from "../../../../components/renderer/RenderNode"; import { View3D } from "../../../../core/View3D"; import { ProfilerUtil } from "../../../../util/ProfilerUtil"; +import { GlobalBindGroup } from "../../../graphics/webGpu/core/bindGroups/GlobalBindGroup"; import { GPUContext } from "../../GPUContext"; import { EntityCollect } from "../../collect/EntityCollect"; -import { RTResourceConfig } from "../../config/RTResourceConfig"; -import { RTResourceMap } from "../../frame/RTResourceMap"; import { OcclusionSystem } from "../../occlusion/OcclusionSystem"; import { RenderContext } from "../RenderContext"; import { RendererBase } from "../RendererBase"; import { ClusterLightingBuffer } from "../cluster/ClusterLightingBuffer"; import { PassType } from "../state/PassType"; +import { RendererMask } from "../state/RendererMask"; /** * @internal @@ -133,7 +132,8 @@ export class ColorPassRenderer extends RendererBase { continue; if (!renderNode.enable) continue; - + if (renderNode.hasMask(RendererMask.UI) && !renderNode.isRecievePostEffectUI) + continue; if (!renderNode.preInit(this._rendererType)) { renderNode.nodeUpdate(view, this._rendererType, this.rendererPassState, clusterLightingBuffer); } diff --git a/src/gfx/renderJob/passRenderer/color/GUIPassRenderer.ts b/src/gfx/renderJob/passRenderer/color/GUIPassRenderer.ts new file mode 100644 index 00000000..c9b768e5 --- /dev/null +++ b/src/gfx/renderJob/passRenderer/color/GUIPassRenderer.ts @@ -0,0 +1,109 @@ +import { Engine3D } from "../../../../Engine3D"; +import { RenderNode } from "../../../../components/renderer/RenderNode"; +import { View3D } from "../../../../core/View3D"; +import { GlobalBindGroup } from "../../../graphics/webGpu/core/bindGroups/GlobalBindGroup"; +import { GPUContext } from "../../GPUContext"; +import { EntityCollect } from "../../collect/EntityCollect"; +import { GBufferFrame } from "../../frame/GBufferFrame"; +import { RTFrame } from "../../frame/RTFrame"; +import { OcclusionSystem } from "../../occlusion/OcclusionSystem"; +import { RenderContext } from "../RenderContext"; +import { RendererBase } from "../RendererBase"; +import { ClusterLightingBuffer } from "../cluster/ClusterLightingBuffer"; +import { PassType } from "../state/PassType"; +import { RendererMask } from "../state/RendererMask"; + +/** + * @internal + * Base Color Renderer + * @author sirxu + * @group Post + */ +export class GUIPassRenderer extends RendererBase { + constructor() { + super(); + this.passType = PassType.UI; + } + + public compute(view: View3D, occlusionSystem: OcclusionSystem): void { + let command = GPUContext.beginCommandEncoder(); + let src = GPUContext.lastRenderPassState.getLastRenderTexture(); + let dest = GBufferFrame.getGUIBufferFrame().getColorTexture(); + GPUContext.copyTexture(command, src, dest); + GPUContext.endCommandEncoder(command); + } + + public render(view: View3D, occlusionSystem: OcclusionSystem, clusterLightingBuffer?: ClusterLightingBuffer, maskTr: boolean = false) { + this.renderContext.clean(); + + + let scene = view.scene; + let camera = view.camera; + + GlobalBindGroup.updateCameraGroup(camera); + + this.rendererPassState.camera3D = camera; + + let collectInfo = EntityCollect.instance.getRenderNodes(scene, camera); + + { + this.renderContext.specialtRenderPass(); + + let renderPassEncoder = this.renderContext.encoder; + + + if (collectInfo.opaqueList) { + GPUContext.bindCamera(renderPassEncoder, camera); + this.drawNodes(view, this.renderContext, collectInfo.opaqueList, occlusionSystem, clusterLightingBuffer); + } + } + + { + + let renderPassEncoder = this.renderContext.encoder; + + + if (!maskTr && collectInfo.transparentList) { + GPUContext.bindCamera(renderPassEncoder, camera); + this.drawNodes(view, this.renderContext, collectInfo.transparentList, occlusionSystem, clusterLightingBuffer); + } + this.renderContext.endRenderPass(); + } + + } + + public drawNodes(view: View3D, renderContext: RenderContext, nodes: RenderNode[], occlusionSystem: OcclusionSystem, clusterLightingBuffer: ClusterLightingBuffer) { + let viewRenderList = EntityCollect.instance.getRenderShaderCollect(view); + if (viewRenderList) { + for (const renderList of viewRenderList) { + let nodeMap = renderList[1]; + for (const iterator of nodeMap) { + let node = iterator[1]; + if (node.preInit(this._rendererType)) { + node.nodeUpdate(view, this._rendererType, this.rendererPassState, clusterLightingBuffer); + break; + } + } + } + + for (let i = Engine3D.setting.render.drawOpMin; i < Math.min(nodes.length, Engine3D.setting.render.drawOpMax); ++i) { + let renderNode = nodes[i]; + if (!renderNode.transform.enable) + continue; + if (!renderNode.enable) + continue; + if (!renderNode.hasMask(RendererMask.UI) || renderNode.isRecievePostEffectUI) + continue; + if (!renderNode.preInit(this._rendererType)) { + renderNode.nodeUpdate(view, this._rendererType, this.rendererPassState, clusterLightingBuffer); + } + renderNode.renderPass(view, this.passType, this.renderContext); + } + } + } + + + protected occlusionRenderNodeTest(i: number, id: number, occlusionSystem: OcclusionSystem): boolean { + return occlusionSystem.zDepthRenderNodeTest(id) > 0; + } +} diff --git a/src/gfx/renderJob/passRenderer/post/PostRenderer.ts b/src/gfx/renderJob/passRenderer/post/PostRenderer.ts index 4f3352e7..0be0a34a 100644 --- a/src/gfx/renderJob/passRenderer/post/PostRenderer.ts +++ b/src/gfx/renderJob/passRenderer/post/PostRenderer.ts @@ -3,7 +3,9 @@ import { ShaderLib } from "../../../../assets/shader/ShaderLib"; import { FullQuad_vert_wgsl } from "../../../../assets/shader/quad/Quad_shader"; import { View3D } from "../../../../core/View3D"; import { ViewQuad } from "../../../../core/ViewQuad"; +import { Texture } from "../../../graphics/webGpu/core/texture/Texture"; import { GPUContext } from "../../GPUContext"; +import { GBufferFrame } from "../../frame/GBufferFrame"; import { RTFrame } from "../../frame/RTFrame"; import { PostBase } from "../../post/PostBase"; import { RendererBase } from "../RendererBase"; @@ -67,15 +69,13 @@ export class PostRenderer extends RendererBase { v.render(view, command); } }); + GPUContext.endCommandEncoder(command); + } - let lastTexture = GPUContext.lastRenderPassState.getLastRenderTexture(); - this.finalQuadView.renderToViewQuad(view, this.finalQuadView, command, lastTexture); - { - if (this.debugViewQuads.length) { - let debugIndex = Engine3D.setting.render.debugQuad; - if (debugIndex >= 0) this.debugViewQuads[debugIndex].renderToViewQuad(view, this.debugViewQuads[debugIndex], command, this.debugTextures[debugIndex]); - } - } + public presentContent(view: View3D, texture: Texture) { + let command = GPUContext.beginCommandEncoder(); + this.finalQuadView.renderToViewQuad(view, this.finalQuadView, command, texture); GPUContext.endCommandEncoder(command); } + } diff --git a/src/gfx/renderJob/passRenderer/state/PassType.ts b/src/gfx/renderJob/passRenderer/state/PassType.ts index 18a1200c..8fab3a8e 100644 --- a/src/gfx/renderJob/passRenderer/state/PassType.ts +++ b/src/gfx/renderJob/passRenderer/state/PassType.ts @@ -6,11 +6,11 @@ export enum PassType { REFLECTION = 1 << 1, POSITION = 1 << 2, GRAPHIC = 1 << 3, - GI = 1 << 4, Cluster = 1 << 5, SHADOW = 1 << 6, POINT_SHADOW = 1 << 7, POST = 1 << 8, DEPTH = 1 << 9, + UI = 1 << 10, } diff --git a/src/index.ts b/src/index.ts index 28acd9a2..03684f4d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -351,6 +351,7 @@ export * from "./gfx/renderJob/passRenderer/cluster/ClusterConfig" export * from "./gfx/renderJob/passRenderer/cluster/ClusterLightingBuffer" export * from "./gfx/renderJob/passRenderer/cluster/ClusterLightingRender" export * from "./gfx/renderJob/passRenderer/color/ColorPassRenderer" +export * from "./gfx/renderJob/passRenderer/color/GUIPassRenderer" export * from "./gfx/renderJob/passRenderer/cubeRenderer/ReflectionRenderer" export * from "./gfx/renderJob/passRenderer/ddgi/DDGIIrradianceComputePass" export * from "./gfx/renderJob/passRenderer/ddgi/DDGIIrradianceGPUBufferReader" From a1d1b2aa9fc0b6abc55ad7894312f1100f6b466e Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Tue, 23 Jul 2024 18:18:21 +0800 Subject: [PATCH 03/25] feat(graphic): move graphic3D to @orillusion/graphic (#427) --- .../compute/graphic3d}/Graphic3DShader.ts | 0 .../compute/graphic3d}/GraphicDecCompute.ts | 0 .../graphic3d}/GraphicDynamicCompute.ts | 0 .../compute/graphic3d}/GraphicFaceComput2.ts | 0 .../compute/graphic3d}/GraphicFaceCompute.ts | 0 .../compute/graphic3d}/GraphicFaceCompute3.ts | 0 .../compute/graphic3d}/GraphicLineCompute.ts | 0 .../compute/graphic3d}/GraphicTrailCompute.ts | 0 .../graphic3d}/GraphicTrailCompute2.ts | 0 packages/graphic/index.ts | 17 +- packages/graphic/package.json | 4 +- .../renderer}/Graphic3DBatchRenderer.ts | 12 +- .../renderer}/Graphic3DFillRenderer.ts | 2 +- .../renderer}/Graphic3DFixedRenderMaterial.ts | 5 +- .../renderer}/Graphic3DLineBatchRenderer.ts | 2 +- .../graphic/renderer}/Graphic3DRender.ts | 12 +- .../graphic/renderer}/Graphics3DShape.ts | 4 +- packages/graphic/renderer/GrassRenderer.ts | 5 +- packages/graphic/renderer/Shape3DMaker.ts | 3 +- packages/graphic/renderer/Shape3DRenderer.ts | 5 +- .../renderer/graphic3d}/DynamicDrawStruct.ts | 2 +- .../graphic3d}/DynamicFaceRenderer.ts | 0 .../renderer/graphic3d}/Float32ArrayUtil.ts | 2 +- .../graphic3d}/Graphic3DFaceRenderer.ts | 21 +-- .../renderer/graphic3d}/Graphic3DMesh.ts | 6 +- .../graphic3d}/Graphic3DMeshRenderer.ts | 14 +- .../graphic3d}/Graphic3DRibbonRenderer.ts | 21 +-- .../graphic/renderer/graphic3d}/ShapeInfo.ts | 4 +- .../graphic/renderer/shape3d/LineShape3D.ts | 2 +- .../graphic/renderer/shape3d/Path3DShape3D.ts | 3 +- packages/graphic/renderer/shape3d/Shape3D.ts | 3 +- packages/graphic/tsconfig.json | 3 +- .../animation/Sample_CameraPathAnimation.ts | 18 +- samples/base/Sample_BoundingBox.ts | 9 +- samples/base/Sample_ComponentLife.ts | 2 - samples/graphic/Sample_GraphicGrass.ts | 47 +---- samples/graphic/Sample_GraphicLine.ts | 22 ++- samples/graphic/Sample_GraphicMeshWave.ts | 18 +- samples/graphic/Sample_GraphicMesh_0.ts | 110 +---------- samples/graphic/Sample_GraphicMesh_1.ts | 104 +---------- samples/graphic/Sample_GraphicMesh_2.ts | 101 +--------- samples/graphic/Sample_GraphicMesh_3.ts | 48 +---- samples/graphic/Sample_GraphicMesh_4.ts | 28 +-- samples/graphic/Sample_GraphicMesh_5.ts | 96 +--------- samples/graphic/Sample_GraphicMesh_6.ts | 50 ++--- samples/graphic/Sample_GraphicMesh_7.ts | 37 ++-- samples/graphic/Sample_GraphicMesh_Color.ts | 173 ------------------ .../graphic/Sample_GraphicMesh_SpriteSheet.ts | 42 +---- samples/graphic/Sample_Shape3D.ts | 8 +- samples/graphic/Sample_Shape3DPath2D.ts | 6 +- samples/graphic/Sample_Shape3DPath3D.ts | 5 +- samples/graphic/_Sample_GraphicShape.ts | 155 ---------------- samples/graphic/_Sample_GraphicTrailing.ts | 15 +- samples/graphic/_Sample_GraphicTrailing2.ts | 16 +- samples/graphic/_Sample_GraphicTrailing3.ts | 15 +- samples/lights/Sample_CSM.ts | 7 +- samples/octree/Sample_OctTreeBox.ts | 11 +- samples/octree/Sample_OctTreeFrustum.ts | 13 +- samples/octree/Sample_OctTreeRay.ts | 12 +- samples/utils/GUIShape3D.ts | 2 +- src/components/lights/Light.ts | 5 +- src/components/lights/PointLight.ts | 5 +- src/components/lights/SpotLight.ts | 5 +- src/components/renderer/MeshRenderer.ts | 5 +- src/core/View3D.ts | 14 -- src/gfx/renderJob/collect/EntityCollect.ts | 8 +- .../passRenderer/state/RendererMask.ts | 1 + src/index.ts | 23 --- src/loader/parser/prefab/prefabData/APatch.ts | 26 +-- 69 files changed, 272 insertions(+), 1142 deletions(-) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/Graphic3DShader.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicDecCompute.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicDynamicCompute.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicFaceComput2.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicFaceCompute.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicFaceCompute3.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicLineCompute.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicTrailCompute.ts (100%) rename {src/assets/shader/graphic => packages/graphic/compute/graphic3d}/GraphicTrailCompute2.ts (100%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphic3DBatchRenderer.ts (90%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphic3DFillRenderer.ts (75%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphic3DFixedRenderMaterial.ts (77%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphic3DLineBatchRenderer.ts (75%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphic3DRender.ts (97%) rename {src/gfx/renderJob/passRenderer/graphic => packages/graphic/renderer}/Graphics3DShape.ts (97%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/DynamicDrawStruct.ts (54%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/DynamicFaceRenderer.ts (100%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/Float32ArrayUtil.ts (86%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/Graphic3DFaceRenderer.ts (85%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/Graphic3DMesh.ts (90%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/Graphic3DMeshRenderer.ts (76%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/Graphic3DRibbonRenderer.ts (81%) rename {src/gfx/renderJob/passRenderer/graphic/new => packages/graphic/renderer/graphic3d}/ShapeInfo.ts (73%) delete mode 100644 samples/graphic/Sample_GraphicMesh_Color.ts delete mode 100644 samples/graphic/_Sample_GraphicShape.ts diff --git a/src/assets/shader/graphic/Graphic3DShader.ts b/packages/graphic/compute/graphic3d/Graphic3DShader.ts similarity index 100% rename from src/assets/shader/graphic/Graphic3DShader.ts rename to packages/graphic/compute/graphic3d/Graphic3DShader.ts diff --git a/src/assets/shader/graphic/GraphicDecCompute.ts b/packages/graphic/compute/graphic3d/GraphicDecCompute.ts similarity index 100% rename from src/assets/shader/graphic/GraphicDecCompute.ts rename to packages/graphic/compute/graphic3d/GraphicDecCompute.ts diff --git a/src/assets/shader/graphic/GraphicDynamicCompute.ts b/packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts similarity index 100% rename from src/assets/shader/graphic/GraphicDynamicCompute.ts rename to packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts diff --git a/src/assets/shader/graphic/GraphicFaceComput2.ts b/packages/graphic/compute/graphic3d/GraphicFaceComput2.ts similarity index 100% rename from src/assets/shader/graphic/GraphicFaceComput2.ts rename to packages/graphic/compute/graphic3d/GraphicFaceComput2.ts diff --git a/src/assets/shader/graphic/GraphicFaceCompute.ts b/packages/graphic/compute/graphic3d/GraphicFaceCompute.ts similarity index 100% rename from src/assets/shader/graphic/GraphicFaceCompute.ts rename to packages/graphic/compute/graphic3d/GraphicFaceCompute.ts diff --git a/src/assets/shader/graphic/GraphicFaceCompute3.ts b/packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts similarity index 100% rename from src/assets/shader/graphic/GraphicFaceCompute3.ts rename to packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts diff --git a/src/assets/shader/graphic/GraphicLineCompute.ts b/packages/graphic/compute/graphic3d/GraphicLineCompute.ts similarity index 100% rename from src/assets/shader/graphic/GraphicLineCompute.ts rename to packages/graphic/compute/graphic3d/GraphicLineCompute.ts diff --git a/src/assets/shader/graphic/GraphicTrailCompute.ts b/packages/graphic/compute/graphic3d/GraphicTrailCompute.ts similarity index 100% rename from src/assets/shader/graphic/GraphicTrailCompute.ts rename to packages/graphic/compute/graphic3d/GraphicTrailCompute.ts diff --git a/src/assets/shader/graphic/GraphicTrailCompute2.ts b/packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts similarity index 100% rename from src/assets/shader/graphic/GraphicTrailCompute2.ts rename to packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts diff --git a/packages/graphic/index.ts b/packages/graphic/index.ts index 689d3212..f5d81db2 100644 --- a/packages/graphic/index.ts +++ b/packages/graphic/index.ts @@ -1,5 +1,3 @@ -export * from "./compute/grass/GrassAnimCompute_cs" -export * from "./compute/grass/GrassGeometryCompute_cs" export * from "./renderer/GrassRenderer" export * from "./renderer/Shape3DRenderer" export * from "./renderer/Shape3DMaker" @@ -12,3 +10,18 @@ export * from "./renderer/shape3d/LineShape3D" export * from "./renderer/shape3d/Shape3D" export * from "./renderer/shape3d/Path3DShape3D" export * from "./renderer/shape3d/Path2DShape3D" + +export * from "./renderer/Graphics3DShape" +export * from "./renderer/Graphic3DRender" +export * from "./renderer/Graphic3DBatchRenderer" +export * from "./renderer/Graphic3DLineBatchRenderer" +export * from "./renderer/Graphic3DFixedRenderMaterial" +export * from "./renderer/Graphic3DFillRenderer" +export * from "./renderer/graphic3d/DynamicDrawStruct" +export * from "./renderer/graphic3d/DynamicFaceRenderer" +export * from "./renderer/graphic3d/Float32ArrayUtil" +export * from "./renderer/graphic3d/Graphic3DFaceRenderer" +export * from "./renderer/graphic3d/Graphic3DMesh" +export * from "./renderer/graphic3d/Graphic3DMeshRenderer" +export * from "./renderer/graphic3d/Graphic3DRibbonRenderer" +export * from "./renderer/graphic3d/ShapeInfo" \ No newline at end of file diff --git a/packages/graphic/package.json b/packages/graphic/package.json index fe5bd929..d6c5e73e 100644 --- a/packages/graphic/package.json +++ b/packages/graphic/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/graphic", - "version": "0.1.1", + "version": "0.2.0", "author": "Orillusion", "description": "Orillusion graphic Plugin", "main": "./dist/graphic.umd.js", @@ -22,7 +22,7 @@ "url": "git+https://github.com/Orillusion/orillusion.git" }, "dependencies": { - "@orillusion/core": "^0.7.0", + "@orillusion/core": "^0.8.0", "earcut": "^2.2.4" } } diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphic3DBatchRenderer.ts b/packages/graphic/renderer/Graphic3DBatchRenderer.ts similarity index 90% rename from src/gfx/renderJob/passRenderer/graphic/Graphic3DBatchRenderer.ts rename to packages/graphic/renderer/Graphic3DBatchRenderer.ts index 5fbc857b..7c71a5ff 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphic3DBatchRenderer.ts +++ b/packages/graphic/renderer/Graphic3DBatchRenderer.ts @@ -1,12 +1,7 @@ -import { RenderNode } from "../../../../components/renderer/RenderNode"; -import { View3D } from "../../../../core/View3D"; -import { Color } from "../../../../math/Color"; -import { Vector3 } from "../../../../math/Vector3"; -import { RendererPassState } from "../state/RendererPassState"; -import { PassType } from "../state/PassType"; + +import { ClusterLightingBuffer, Color, GeometryBase, PassType, RendererMask, RendererPassState, RenderNode, Vector3, VertexAttributeName, View3D } from "@orillusion/core"; import { Graphics3DShape } from "./Graphics3DShape"; -import { ClusterLightingBuffer } from "../cluster/ClusterLightingBuffer"; -import { GeometryBase, Graphic3DFixedRenderMaterial, VertexAttributeName } from "../../../.."; +import { Graphic3DFixedRenderMaterial } from "./Graphic3DFixedRenderMaterial"; /** * @internal @@ -29,6 +24,7 @@ export class Graphic3DBatchRenderer extends RenderNode { public init() { super.init(); + this.addRendererMask(RendererMask.Graphic3D); this.castGI = false; this.castShadow = false; this.geometry = new GeometryBase(); diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphic3DFillRenderer.ts b/packages/graphic/renderer/Graphic3DFillRenderer.ts similarity index 75% rename from src/gfx/renderJob/passRenderer/graphic/Graphic3DFillRenderer.ts rename to packages/graphic/renderer/Graphic3DFillRenderer.ts index 854ca759..d6a39a7c 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphic3DFillRenderer.ts +++ b/packages/graphic/renderer/Graphic3DFillRenderer.ts @@ -1,4 +1,4 @@ -import { GPUPrimitiveTopology } from "../../../graphics/webGpu/WebGPUConst"; +import { GPUPrimitiveTopology } from "@orillusion/core"; import { Graphic3DBatchRenderer } from "./Graphic3DBatchRenderer"; /** diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphic3DFixedRenderMaterial.ts b/packages/graphic/renderer/Graphic3DFixedRenderMaterial.ts similarity index 77% rename from src/gfx/renderJob/passRenderer/graphic/Graphic3DFixedRenderMaterial.ts rename to packages/graphic/renderer/Graphic3DFixedRenderMaterial.ts index 5eb07ba5..e3c423cd 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphic3DFixedRenderMaterial.ts +++ b/packages/graphic/renderer/Graphic3DFixedRenderMaterial.ts @@ -1,6 +1,5 @@ -import { Engine3D, GPUPrimitiveTopology, RenderShaderPass, Shader, ShaderLib, Texture } from "../../../.."; -import { Graphic3DShader } from "../../../../assets/shader/graphic/Graphic3DShader"; -import { Material } from "../../../../materials/Material"; +import { GPUPrimitiveTopology, RenderShaderPass, Shader, ShaderLib, Material } from "@orillusion/core"; +import { Graphic3DShader } from "../compute/graphic3d/Graphic3DShader"; /** * @internal diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphic3DLineBatchRenderer.ts b/packages/graphic/renderer/Graphic3DLineBatchRenderer.ts similarity index 75% rename from src/gfx/renderJob/passRenderer/graphic/Graphic3DLineBatchRenderer.ts rename to packages/graphic/renderer/Graphic3DLineBatchRenderer.ts index fed71b5f..c4c78ded 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphic3DLineBatchRenderer.ts +++ b/packages/graphic/renderer/Graphic3DLineBatchRenderer.ts @@ -1,4 +1,4 @@ -import { GPUPrimitiveTopology } from "../../../graphics/webGpu/WebGPUConst"; +import { GPUPrimitiveTopology } from "@orillusion/core"; import { Graphic3DBatchRenderer } from "./Graphic3DBatchRenderer"; /** diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphic3DRender.ts b/packages/graphic/renderer/Graphic3DRender.ts similarity index 97% rename from src/gfx/renderJob/passRenderer/graphic/Graphic3DRender.ts rename to packages/graphic/renderer/Graphic3DRender.ts index edf7597c..cd56700d 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphic3DRender.ts +++ b/packages/graphic/renderer/Graphic3DRender.ts @@ -1,16 +1,7 @@ -import { GeometryBase } from "../../../../core/geometry/GeometryBase"; -import { Color } from "../../../../math/Color"; -import { Vector3 } from "../../../../math/Vector3"; -import { DEGREES_TO_RADIANS } from "../../../../math/MathUtil"; -import { Transform } from "../../../../components/Transform"; -import { BoundingBox } from "../../../../core/bound/BoundingBox"; -import { Camera3D } from "../../../../core/Camera3D"; -import { CameraType } from "../../../../core/CameraType"; -import { Object3D } from "../../../../core/entities/Object3D"; +import { BoundingBox, BoundUtil, Camera3D, CameraType, Color, DEGREES_TO_RADIANS, GeometryBase, Object3D, Transform, Vector3 } from "@orillusion/core"; import { Graphics3DShape } from "./Graphics3DShape"; import { Graphic3DFillRenderer } from "./Graphic3DFillRenderer"; import { Graphic3DLineRenderer } from "./Graphic3DLineBatchRenderer"; -import { BoundUtil } from "../../../../util/BoundUtil"; export class Graphic3D extends Object3D { protected mLineRender: Graphic3DLineRenderer; @@ -18,6 +9,7 @@ export class Graphic3D extends Object3D { constructor() { super(); + this.name = 'graphic3D'; this.mLineRender = this.addComponent(Graphic3DLineRenderer); this.mFillRender = this.addComponent(Graphic3DFillRenderer); } diff --git a/src/gfx/renderJob/passRenderer/graphic/Graphics3DShape.ts b/packages/graphic/renderer/Graphics3DShape.ts similarity index 97% rename from src/gfx/renderJob/passRenderer/graphic/Graphics3DShape.ts rename to packages/graphic/renderer/Graphics3DShape.ts index 274362a9..6d8b1ff6 100644 --- a/src/gfx/renderJob/passRenderer/graphic/Graphics3DShape.ts +++ b/packages/graphic/renderer/Graphics3DShape.ts @@ -1,6 +1,4 @@ -import { Color } from "../../../../math/Color"; -import { DEGREES_TO_RADIANS } from "../../../../math/MathUtil"; -import { Vector3 } from "../../../../math/Vector3"; +import { Color, DEGREES_TO_RADIANS, Vector3 } from "@orillusion/core"; /** * @internal diff --git a/packages/graphic/renderer/GrassRenderer.ts b/packages/graphic/renderer/GrassRenderer.ts index 2363a636..210a55d8 100644 --- a/packages/graphic/renderer/GrassRenderer.ts +++ b/packages/graphic/renderer/GrassRenderer.ts @@ -1,6 +1,9 @@ -import { ComputeShader, DynamicDrawStruct, DynamicFaceRenderer, Struct, graphicDynamicCompute } from "@orillusion/core"; +import { ComputeShader } from "@orillusion/core"; +import { graphicDynamicCompute } from "../compute/graphic3d/GraphicDynamicCompute"; import { GrassGeometryCompute_cs } from "../compute/grass/GrassGeometryCompute_cs"; +import { DynamicDrawStruct } from "./graphic3d/DynamicDrawStruct"; +import { DynamicFaceRenderer } from "./graphic3d/DynamicFaceRenderer"; export class GrassNodeStruct extends DynamicDrawStruct { grassCount: number = 1; diff --git a/packages/graphic/renderer/Shape3DMaker.ts b/packages/graphic/renderer/Shape3DMaker.ts index 00a88322..f820bc82 100644 --- a/packages/graphic/renderer/Shape3DMaker.ts +++ b/packages/graphic/renderer/Shape3DMaker.ts @@ -1,4 +1,4 @@ -import { BitmapTexture2DArray, Graphic3DMesh, Scene3D, Vector2, Vector3 } from "@orillusion/core"; +import { BitmapTexture2DArray, Scene3D, Vector2 } from "@orillusion/core"; import { Shape3DRenderer } from "./Shape3DRenderer"; import { RoundRectShape3D } from "./shape3d/RoundRectShape3D"; import { EllipseShape3D } from "./shape3d/EllipseShape3D"; @@ -9,6 +9,7 @@ import { QuadraticCurveShape3D } from "./shape3d/QuadraticCurveShape3D"; import { CurveShape3D } from "./shape3d/CurveShape3D"; import { Path2DShape3D } from "./shape3d/Path2DShape3D"; import { Path3DShape3D } from "./shape3d/Path3DShape3D"; +import { Graphic3DMesh } from "./graphic3d/Graphic3DMesh"; /** diff --git a/packages/graphic/renderer/Shape3DRenderer.ts b/packages/graphic/renderer/Shape3DRenderer.ts index 0439a0ba..a753d764 100644 --- a/packages/graphic/renderer/Shape3DRenderer.ts +++ b/packages/graphic/renderer/Shape3DRenderer.ts @@ -1,9 +1,12 @@ -import { BitmapTexture2DArray, ComputeShader, Ctor, DynamicDrawStruct, DynamicFaceRenderer, Matrix4, Object3D, OrderMap, StorageGPUBuffer, UniformGPUBuffer, Vector2, Vector3, Vector4, View3D, graphicDynamicCompute } from "@orillusion/core"; +import { BitmapTexture2DArray, ComputeShader, Ctor, Object3D, OrderMap, StorageGPUBuffer, UniformGPUBuffer, Vector3, Vector4, View3D } from "@orillusion/core"; import { Shape3DVertexCompute_cs } from "../compute/shape3d/Shape3DVertexCompute_cs"; import { Shape3DKeyPointCompute_cs } from "../compute/shape3d/Shape3DKeyPointCompute_cs"; import { Shape3D } from "./shape3d/Shape3D"; import { Shape3DVertexFillZero_cs } from "../compute/shape3d/Shape3DVertexFillZero_cs"; +import { DynamicFaceRenderer } from "./graphic3d/DynamicFaceRenderer"; +import { DynamicDrawStruct } from "./graphic3d/DynamicDrawStruct"; +import { graphicDynamicCompute } from "../compute/graphic3d/GraphicDynamicCompute"; export class Shape3DRenderer extends DynamicFaceRenderer { private _destPathBuffer: StorageGPUBuffer; diff --git a/src/gfx/renderJob/passRenderer/graphic/new/DynamicDrawStruct.ts b/packages/graphic/renderer/graphic3d/DynamicDrawStruct.ts similarity index 54% rename from src/gfx/renderJob/passRenderer/graphic/new/DynamicDrawStruct.ts rename to packages/graphic/renderer/graphic3d/DynamicDrawStruct.ts index e90103a2..ec77b4f3 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/DynamicDrawStruct.ts +++ b/packages/graphic/renderer/graphic3d/DynamicDrawStruct.ts @@ -1,4 +1,4 @@ -import { Struct } from "../../../../../util/struct/Struct"; +import { Struct } from "@orillusion/core"; export class DynamicDrawStruct extends Struct { // @NonSerialize diff --git a/src/gfx/renderJob/passRenderer/graphic/new/DynamicFaceRenderer.ts b/packages/graphic/renderer/graphic3d/DynamicFaceRenderer.ts similarity index 100% rename from src/gfx/renderJob/passRenderer/graphic/new/DynamicFaceRenderer.ts rename to packages/graphic/renderer/graphic3d/DynamicFaceRenderer.ts diff --git a/src/gfx/renderJob/passRenderer/graphic/new/Float32ArrayUtil.ts b/packages/graphic/renderer/graphic3d/Float32ArrayUtil.ts similarity index 86% rename from src/gfx/renderJob/passRenderer/graphic/new/Float32ArrayUtil.ts rename to packages/graphic/renderer/graphic3d/Float32ArrayUtil.ts index 88d59170..2f5e5e6d 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/Float32ArrayUtil.ts +++ b/packages/graphic/renderer/graphic3d/Float32ArrayUtil.ts @@ -1,4 +1,4 @@ -import { Vector4 } from "../../../../.."; +import { Vector4 } from "@orillusion/core"; export class Float32ArrayUtil { diff --git a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DFaceRenderer.ts b/packages/graphic/renderer/graphic3d/Graphic3DFaceRenderer.ts similarity index 85% rename from src/gfx/renderJob/passRenderer/graphic/new/Graphic3DFaceRenderer.ts rename to packages/graphic/renderer/graphic3d/Graphic3DFaceRenderer.ts index 4475b043..68ea5d7a 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DFaceRenderer.ts +++ b/packages/graphic/renderer/graphic3d/Graphic3DFaceRenderer.ts @@ -1,22 +1,7 @@ -import { GraphicLineCompute } from "../../../../../assets/shader/graphic/GraphicLineCompute"; -import { MeshRenderer } from "../../../../../components/renderer/MeshRenderer"; -import { View3D } from "../../../../../core/View3D"; -import { Object3D } from "../../../../../core/entities/Object3D"; -import { UnLitTexArrayMaterial } from "../../../../../materials/UnLitTexArrayMaterial"; -import { Color } from "../../../../../math/Color"; -import { Vector3 } from "../../../../../math/Vector3"; -import { Vector4 } from "../../../../../math/Vector4"; -import { TriGeometry } from "../../../../../shape/TriGeometry"; -import { BitmapTexture2DArray } from "../../../../../textures/BitmapTexture2DArray"; -import { GeometryUtil } from "../../../../../util/GeometryUtil"; -import { Struct } from "../../../../../util/struct/Struct"; -import { GlobalBindGroup } from "../../../../graphics/webGpu/core/bindGroups/GlobalBindGroup"; -import { StorageGPUBuffer } from "../../../../graphics/webGpu/core/buffer/StorageGPUBuffer"; -import { StructStorageGPUBuffer } from "../../../../graphics/webGpu/core/buffer/StructStorageGPUBuffer"; -import { ComputeShader } from "../../../../graphics/webGpu/shader/ComputeShader"; -import { GPUContext } from "../../../GPUContext"; -import { Float32ArrayUtil } from "./Float32ArrayUtil"; +import { Struct, MeshRenderer, BitmapTexture2DArray, StorageGPUBuffer, ComputeShader, StructStorageGPUBuffer, TriGeometry, UnLitTexArrayMaterial, Object3D, Color, Vector4, GlobalBindGroup, View3D, GPUContext } from "@orillusion/core"; import { ShapeInfo } from "./ShapeInfo"; +import { GraphicLineCompute } from "../../compute/graphic3d/GraphicLineCompute"; + export enum LineJoin { bevel = 0, diff --git a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMesh.ts b/packages/graphic/renderer/graphic3d/Graphic3DMesh.ts similarity index 90% rename from src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMesh.ts rename to packages/graphic/renderer/graphic3d/Graphic3DMesh.ts index 2f721311..ced66e6b 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMesh.ts +++ b/packages/graphic/renderer/graphic3d/Graphic3DMesh.ts @@ -1,12 +1,8 @@ -import { Scene3D } from "../../../../../core/Scene3D"; -import { Object3D } from "../../../../../core/entities/Object3D"; -import { GeometryBase } from "../../../../../core/geometry/GeometryBase"; -import { BitmapTexture2DArray } from "../../../../../textures/BitmapTexture2DArray"; +import { GeometryBase, Scene3D, BitmapTexture2DArray, Object3D, Ctor } from "@orillusion/core"; import { Graphic3DMeshRenderer } from "./Graphic3DMeshRenderer"; import { Graphic3DRibbonRenderer } from "./Graphic3DRibbonRenderer"; import { Graphic3DFaceRenderer } from "./Graphic3DFaceRenderer"; import { DynamicFaceRenderer } from "./DynamicFaceRenderer"; -import { Ctor } from "../../../../.."; import { DynamicDrawStruct } from "./DynamicDrawStruct"; export class Graphic3DMesh { diff --git a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMeshRenderer.ts b/packages/graphic/renderer/graphic3d/Graphic3DMeshRenderer.ts similarity index 76% rename from src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMeshRenderer.ts rename to packages/graphic/renderer/graphic3d/Graphic3DMeshRenderer.ts index f45f0c1e..95f2aa36 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DMeshRenderer.ts +++ b/packages/graphic/renderer/graphic3d/Graphic3DMeshRenderer.ts @@ -1,16 +1,4 @@ -import { MeshRenderer } from "../../../../../components/renderer/MeshRenderer"; -import { View3D } from "../../../../../core/View3D"; -import { Object3D } from "../../../../../core/entities/Object3D"; -import { GeometryBase } from "../../../../../core/geometry/GeometryBase"; -import { UnLitTexArrayMaterial } from "../../../../../materials/UnLitTexArrayMaterial"; -import { Color } from "../../../../../math/Color"; -import { Vector3 } from "../../../../../math/Vector3"; -import { Vector4 } from "../../../../../math/Vector4"; -import { BitmapTexture2DArray } from "../../../../../textures/BitmapTexture2DArray"; -import { GeometryUtil } from "../../../../../util/GeometryUtil"; -import { StorageGPUBuffer } from "../../../../graphics/webGpu/core/buffer/StorageGPUBuffer"; -import { ComputeShader } from "../../../../graphics/webGpu/shader/ComputeShader"; -import { GPUContext } from "../../../GPUContext"; +import { MeshRenderer, StorageGPUBuffer, GeometryBase, BitmapTexture2DArray, Object3D, ComputeShader, UnLitTexArrayMaterial, Vector3, Color, Vector4, GeometryUtil, View3D, GPUContext } from "@orillusion/core"; export class Graphic3DMeshRenderer extends MeshRenderer { public transformBuffer: StorageGPUBuffer; diff --git a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DRibbonRenderer.ts b/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts similarity index 81% rename from src/gfx/renderJob/passRenderer/graphic/new/Graphic3DRibbonRenderer.ts rename to packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts index f55d1581..03f37d35 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/Graphic3DRibbonRenderer.ts +++ b/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts @@ -1,22 +1,5 @@ -import { graphicTrailCompute } from "../../../../../assets/shader/graphic/GraphicTrailCompute"; -import { MeshRenderer } from "../../../../../components/renderer/MeshRenderer"; -import { View3D } from "../../../../../core/View3D"; -import { Object3D } from "../../../../../core/entities/Object3D"; -import { GeometryBase } from "../../../../../core/geometry/GeometryBase"; -import { UnLitTexArrayMaterial } from "../../../../../materials/UnLitTexArrayMaterial"; -import { Color } from "../../../../../math/Color"; -import { Vector2 } from "../../../../../math/Vector2"; -import { Vector4 } from "../../../../../math/Vector4"; -import { TrailGeometry } from "../../../../../shape/TrailGeometry"; -import { BitmapTexture2DArray } from "../../../../../textures/BitmapTexture2DArray"; -import { GeometryUtil } from "../../../../../util/GeometryUtil"; -import { NonSerialize } from "../../../../../util/SerializeDecoration"; -import { Struct } from "../../../../../util/struct/Struct"; -import { GlobalBindGroup } from "../../../../graphics/webGpu/core/bindGroups/GlobalBindGroup"; -import { StorageGPUBuffer } from "../../../../graphics/webGpu/core/buffer/StorageGPUBuffer"; -import { StructStorageGPUBuffer } from "../../../../graphics/webGpu/core/buffer/StructStorageGPUBuffer"; -import { ComputeShader } from "../../../../graphics/webGpu/shader/ComputeShader"; -import { GPUContext } from "../../../GPUContext"; +import { BitmapTexture2DArray, Color, ComputeShader, GeometryBase, GeometryUtil, GlobalBindGroup, GPUContext, MeshRenderer, NonSerialize, Object3D, StorageGPUBuffer, Struct, StructStorageGPUBuffer, TrailGeometry, UnLitTexArrayMaterial, Vector2, Vector4, View3D } from "@orillusion/core"; +import { graphicTrailCompute } from '../../compute/graphic3d/GraphicTrailCompute' export enum FaceMode { FaceToCamera, diff --git a/src/gfx/renderJob/passRenderer/graphic/new/ShapeInfo.ts b/packages/graphic/renderer/graphic3d/ShapeInfo.ts similarity index 73% rename from src/gfx/renderJob/passRenderer/graphic/new/ShapeInfo.ts rename to packages/graphic/renderer/graphic3d/ShapeInfo.ts index e5382527..3fd07745 100644 --- a/src/gfx/renderJob/passRenderer/graphic/new/ShapeInfo.ts +++ b/packages/graphic/renderer/graphic3d/ShapeInfo.ts @@ -1,6 +1,4 @@ -import { Vector4 } from "../../../../../math/Vector4"; -import { NonSerialize } from "../../../../../util/SerializeDecoration"; -import { Struct } from "../../../../../util/struct/Struct"; +import { NonSerialize, Struct, Vector4 } from "@orillusion/core"; export class ShapeInfo extends Struct { public shapeIndex: number = 0; //face,poly,line,cycle,rectangle,box,sphere diff --git a/packages/graphic/renderer/shape3d/LineShape3D.ts b/packages/graphic/renderer/shape3d/LineShape3D.ts index 39626e9f..237b27a8 100644 --- a/packages/graphic/renderer/shape3d/LineShape3D.ts +++ b/packages/graphic/renderer/shape3d/LineShape3D.ts @@ -1,4 +1,4 @@ -import { Vector3, LineJoin } from "@orillusion/core"; +import { LineJoin } from "../graphic3d/Graphic3DFaceRenderer"; import { Point3D, Shape3D, ShapeTypeEnum } from "./Shape3D"; import earcut from 'earcut'; diff --git a/packages/graphic/renderer/shape3d/Path3DShape3D.ts b/packages/graphic/renderer/shape3d/Path3DShape3D.ts index 7a6f0694..fc7bad5c 100644 --- a/packages/graphic/renderer/shape3d/Path3DShape3D.ts +++ b/packages/graphic/renderer/shape3d/Path3DShape3D.ts @@ -1,6 +1,7 @@ -import { Vector2, deg2Rad, Vector3, Matrix4, rad2Deg, LineJoin } from "@orillusion/core"; +import { Vector2, deg2Rad, Vector3, Matrix4, rad2Deg } from "@orillusion/core"; import { Point3D, Shape3DStruct, ShapeTypeEnum } from "./Shape3D"; import { LineShape3D } from "./LineShape3D"; +import { LineJoin } from "../graphic3d/Graphic3DFaceRenderer"; /** * Define class for drawing path in 3D space. diff --git a/packages/graphic/renderer/shape3d/Shape3D.ts b/packages/graphic/renderer/shape3d/Shape3D.ts index 09db4c89..45a23aaa 100644 --- a/packages/graphic/renderer/shape3d/Shape3D.ts +++ b/packages/graphic/renderer/shape3d/Shape3D.ts @@ -1,4 +1,5 @@ -import { DynamicDrawStruct, Matrix3, LineJoin, Vector2, Color, Vector4, Vector3 } from "@orillusion/core"; +import { Color, Vector4 } from "@orillusion/core"; +import { DynamicDrawStruct } from "../graphic3d/DynamicDrawStruct"; export class Shape3DStruct extends DynamicDrawStruct { public shapeType: number = 0; diff --git a/packages/graphic/tsconfig.json b/packages/graphic/tsconfig.json index 99cc83e4..69d04abd 100644 --- a/packages/graphic/tsconfig.json +++ b/packages/graphic/tsconfig.json @@ -25,6 +25,7 @@ "paths": { "@orillusion/core": ["../../src"], "@orillusion/*": ["../*"] - } + }, + "experimentalDecorators": true } } \ No newline at end of file diff --git a/samples/animation/Sample_CameraPathAnimation.ts b/samples/animation/Sample_CameraPathAnimation.ts index 2fe13b1f..56dc3568 100644 --- a/samples/animation/Sample_CameraPathAnimation.ts +++ b/samples/animation/Sample_CameraPathAnimation.ts @@ -1,5 +1,6 @@ import { Engine3D, Scene3D, View3D, Object3D, Color, Vector3, AtmosphericComponent, CameraUtil, HoverCameraController, DirectLight, KelvinUtil, Time, Object3DUtil, lerpVector3, Camera3D, AxisObject, ColliderComponent, PointerEvent3D, ComponentBase, Plane } from '@orillusion/core'; import { Stats } from '@orillusion/stats'; +import { Graphic3D } from '@orillusion/graphic'; import * as dat from 'dat.gui'; interface PathInfo { @@ -19,6 +20,7 @@ enum CameraModes { class Sample_CameraPathAnimation { view: View3D; camera: Camera3D; + graphic3D: Graphic3D; cameraMode = CameraModes.DualOrbit; guiControl: dat.GUIController; @@ -89,6 +91,10 @@ class Sample_CameraPathAnimation { view.camera = this.camera = camera; view.scene = scene; + // init Graphic3D to draw lines + this.graphic3D = new Graphic3D() + scene.addChild(this.graphic3D) + Engine3D.startRenderView(view); await this.initScene(scene, hoverCtrl); @@ -211,8 +217,8 @@ class Sample_CameraPathAnimation { pathInfo.curvePoints.splice(dataStartIndex, curveSegmentPoints.length, ...curveSegmentPoints); } - this.view.graphic3D.Clear(pathInfo.name); - this.view.graphic3D.drawLines(pathInfo.name, pathInfo.curvePoints, pathInfo.color); + this.graphic3D.Clear(pathInfo.name); + this.graphic3D.drawLines(pathInfo.name, pathInfo.curvePoints, pathInfo.color); } public generateOrUpdateCurve(points: Vector3[], samples: number = 20, tension: number = 0.5, indicesToUpdate?: number[]): Vector3[] { @@ -310,13 +316,13 @@ class Sample_CameraPathAnimation { const changeLine = (show: boolean) => { if (show) { - this.view.graphic3D.drawLines(this.cameraPathInfo.name, this.cameraPathInfo.curvePoints, this.cameraPathInfo.color); - this.view.graphic3D.drawLines(this.targetPathInfo.name, this.targetPathInfo.curvePoints, this.targetPathInfo.color); + this.graphic3D.drawLines(this.cameraPathInfo.name, this.cameraPathInfo.curvePoints, this.cameraPathInfo.color); + this.graphic3D.drawLines(this.targetPathInfo.name, this.targetPathInfo.curvePoints, this.targetPathInfo.color); console.log('camerabasePoints', this.cameraPathInfo.basePoints); console.log('targetbasePoints', this.targetPathInfo.basePoints); } else { - this.view.graphic3D.Clear(this.cameraPathInfo.name); - this.view.graphic3D.Clear(this.targetPathInfo.name); + this.graphic3D.Clear(this.cameraPathInfo.name); + this.graphic3D.Clear(this.targetPathInfo.name); } } } diff --git a/samples/base/Sample_BoundingBox.ts b/samples/base/Sample_BoundingBox.ts index 32b4183a..d0b83f5b 100644 --- a/samples/base/Sample_BoundingBox.ts +++ b/samples/base/Sample_BoundingBox.ts @@ -2,12 +2,14 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; import { Color, Engine3D, Object3D, Object3DUtil, Transform, View3D, } from '@orillusion/core'; import { GUIUtil } from '@samples/utils/GUIUtil'; import { createExampleScene, createSceneParam } from '@samples/utils/ExampleScene'; +import { Graphic3D } from '@orillusion/graphic'; // A sample to show boundingbox class Sample_BoundingBox { view: View3D; box: Object3D; container: Object3D; + graphic3D: Graphic3D async run() { // init engine await Engine3D.init({ renderLoop: () => { this.loop() } }); @@ -25,6 +27,9 @@ class Sample_BoundingBox { this.box = box; this.view = exampleScene.view; + // add a graphic3D to draw lines + this.graphic3D = new Graphic3D(); + exampleScene.scene.addChild(this.graphic3D); let parent = this.container = new Object3D(); parent.addChild(box); @@ -46,8 +51,8 @@ class Sample_BoundingBox { red = new Color(1, 0, 0, 1); green = new Color(0, 1, 0, 1); loop() { - this.view.graphic3D.drawBoundingBox(this.box.instanceID, this.box.bound as any, this.green); - this.view.graphic3D.drawBoundingBox(this.container.instanceID, this.container.bound as any, this.red); + this.graphic3D.drawBoundingBox(this.box.instanceID, this.box.bound as any, this.green); + this.graphic3D.drawBoundingBox(this.container.instanceID, this.container.bound as any, this.red); } } diff --git a/samples/base/Sample_ComponentLife.ts b/samples/base/Sample_ComponentLife.ts index 4b7c2b95..b34f0d5d 100644 --- a/samples/base/Sample_ComponentLife.ts +++ b/samples/base/Sample_ComponentLife.ts @@ -1,6 +1,4 @@ import { Engine3D, Scene3D, CameraUtil, View3D, AtmosphericComponent, ComponentBase, Time, AxisObject, Object3DUtil, KelvinUtil, DirectLight, Object3D, HoverCameraController } from "@orillusion/core"; -import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { GUIUtil } from "@samples/utils/GUIUtil"; // sample use component class Sample_ComponentLife { diff --git a/samples/graphic/Sample_GraphicGrass.ts b/samples/graphic/Sample_GraphicGrass.ts index a70acc96..11a13f8f 100644 --- a/samples/graphic/Sample_GraphicGrass.ts +++ b/samples/graphic/Sample_GraphicGrass.ts @@ -1,7 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Graphic3DMesh, Matrix4, BlendMode, Color, Vector4, GeoJsonStruct, GeoJsonUtil, ShapeInfo, DynamicDrawStruct, Object3DUtil, PostProcessingComponent, GTAOPost } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; -import { GrassNodeStruct, GrassRenderer } from "@orillusion/graphic"; +import { GrassNodeStruct, GrassRenderer, Graphic3DMesh, Graphic3D } from "@orillusion/graphic"; @@ -10,6 +10,7 @@ export class Sample_GraphicGrass { scene: Scene3D; view: View3D; colors: Color[]; + graphic3D: Graphic3D constructor() { } async run() { @@ -39,14 +40,11 @@ export class Sample_GraphicGrass { this.view.scene = this.scene; this.view.camera = camera; - Engine3D.startRenderView(this.view); - - // GUIUtil.renderDebug(); + // add graphic3D to scene + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); - // let post = this.scene.addComponent(PostProcessingComponent); - // let bloom = post.addPost(GTAOPost); - // bloom.bloomIntensity = 1.0 - // GUIUtil.renderBloom(bloom); + Engine3D.startRenderView(this.view); await this.initScene(); @@ -64,47 +62,19 @@ export class Sample_GraphicGrass { directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); directLight.castShadow = false; directLight.intensity = 3; - // GUIUtil.renderDirLight(directLight); this.scene.addChild(this.lightObj3D); } - // let floor = Object3DUtil.GetSingleCube(3000, 1, 3000, 0.5, 0.5, 0.5); - // floor.y = -1; - // this.scene.addChild(floor); - - // let c1 = Object3DUtil.GetSingleCube(20, 20, 20, 0.85, 0.85, 0.85); - // this.scene.addChild(c1); - await this.addGrass(0); - // await this.addGrass(1); - // await this.addGrass(2); - // await this.addGrass(3); - // await this.addGrass(4); } private async addGrass(grassGroup: number) { let texts = []; - // texts.push(await Engine3D.res.loadTexture("terrain/grass/GrassRealistic.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture('terrain/test01/bitmap.png') as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/line3.png") as BitmapTexture2D); - - // texts.push(await Engine3D.res.loadTexture("terrain/grass/single.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("terrain/grass/single2.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("terrain/grass/single3.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("terrain/grass/GrassThick.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("terrain/grass/GrassRealistic.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/line4.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/grid.jpg") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/frame64.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/line_001064.png") as BitmapTexture2D); let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); { - // Unable to fix error related to missing GrassRenderer - // Leaving code as-is - let mr = Graphic3DMesh.drawNode( `path_geojson` + grassGroup, GrassRenderer, @@ -133,11 +103,10 @@ export class Sample_GraphicGrass { node.grassZ = Math.cos(angle) * radiu; } mr.updateShape(); - // Engine3D.views[0].graphic3D.drawFillCircle(`zero`, Vector3.ZERO, 0.5); } } update() { - Engine3D.views[0].graphic3D.drawCameraFrustum(Engine3D.views[0].camera); + this.graphic3D.drawCameraFrustum(Engine3D.views[0].camera); } } diff --git a/samples/graphic/Sample_GraphicLine.ts b/samples/graphic/Sample_GraphicLine.ts index 384d2958..6919cf3c 100644 --- a/samples/graphic/Sample_GraphicLine.ts +++ b/samples/graphic/Sample_GraphicLine.ts @@ -2,10 +2,12 @@ import { createExampleScene, createSceneParam } from "@samples/utils/ExampleScen import { Scene3D, Engine3D, Vector3, Color, AnimationCurve, Keyframe, View3D } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { GUIHelp } from "@orillusion/debug/GUIHelp"; +import { Graphic3D } from '@orillusion/graphic' class Sample_GraphicLine { scene: Scene3D; view: View3D; + graphic3D: Graphic3D; async run() { Engine3D.setting.material.materialChannelDebug = true; @@ -19,6 +21,10 @@ class Sample_GraphicLine { exampleScene.atmosphericSky.exposure = 1.0; this.view = exampleScene.view; this.scene = exampleScene.scene; + + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderViews([exampleScene.view]); let job = Engine3D.getRenderJob(exampleScene.view); await this.initScene(); @@ -27,9 +33,7 @@ class Sample_GraphicLine { } async initScene() { - this.view.graphic3D.drawLines('line1', [Vector3.ZERO, new Vector3(0, 10, 0)], new Color().hexToRGB(Color.RED)); - - + this.graphic3D.drawLines('line1', [Vector3.ZERO, new Vector3(0, 10, 0)], new Color().hexToRGB(Color.RED)); let animCurve = new AnimationCurve(); animCurve.addKeyFrame(new Keyframe(0, 0.5)); @@ -49,13 +53,11 @@ class Sample_GraphicLine { ) ); } - this.view.graphic3D.drawLines('line2', lines, new Color().hexToRGB(Color.RED)); - - this.view.graphic3D.drawBox('box1', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color().hexToRGB(Color.GREEN)); - - this.view.graphic3D.drawCircle('Circle1', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color().hexToRGB(Color.GREEN)); - this.view.graphic3D.drawCircle('Circle2', new Vector3(-15, -5, -5), 5, 15, Vector3.Y_AXIS, new Color().hexToRGB(Color.GREEN)); - this.view.graphic3D.drawCircle('Circle3', new Vector3(-15, -5, -5), 5, 15, Vector3.Z_AXIS, new Color().hexToRGB(Color.GREEN)); + this.graphic3D.drawLines('line2', lines, new Color().hexToRGB(Color.RED)); + this.graphic3D.drawBox('box1', new Vector3(-5, -5, -5), new Vector3(5, 5, 5), new Color().hexToRGB(Color.GREEN)); + this.graphic3D.drawCircle('Circle1', new Vector3(-15, -5, -5), 5, 15, Vector3.X_AXIS, new Color().hexToRGB(Color.GREEN)); + this.graphic3D.drawCircle('Circle2', new Vector3(-15, -5, -5), 5, 15, Vector3.Y_AXIS, new Color().hexToRGB(Color.GREEN)); + this.graphic3D.drawCircle('Circle3', new Vector3(-15, -5, -5), 5, 15, Vector3.Z_AXIS, new Color().hexToRGB(Color.GREEN)); } } diff --git a/samples/graphic/Sample_GraphicMeshWave.ts b/samples/graphic/Sample_GraphicMeshWave.ts index 32e67d24..00af841c 100644 --- a/samples/graphic/Sample_GraphicMeshWave.ts +++ b/samples/graphic/Sample_GraphicMeshWave.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, PrefabParser } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, PrefabParser } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMeshWave { lightObj3D: Object3D; @@ -11,6 +12,7 @@ export class Sample_GraphicMeshWave { height: number; cafe: number = 47; frame: number = 16; + graphic3D: Graphic3D; constructor() { } @@ -22,9 +24,6 @@ export class Sample_GraphicMeshWave { await Engine3D.init({ beforeRender: () => this.update() }); Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - GUIHelp.init(); @@ -41,6 +40,9 @@ export class Sample_GraphicMeshWave { view.scene = this.scene; view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(view); GUIUtil.renderDebug(); @@ -65,9 +67,7 @@ export class Sample_GraphicMeshWave { } let texts = []; - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -80,7 +80,6 @@ export class Sample_GraphicMeshWave { { this.width = 100; this.height = 100; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; @@ -92,11 +91,7 @@ export class Sample_GraphicMeshWave { for (let i = 0; i < this.width * this.height; i++) { const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); - // mr.setTextureID(i, 35); mr.setTextureID(i, 0); - // mr.setTextureID(i, 18); let size = 1.0; element.transform.scaleX = size; @@ -113,7 +108,6 @@ export class Sample_GraphicMeshWave { const element = this.parts[i]; this.wave(i, pos); - element.transform.localPosition = pos; } } diff --git a/samples/graphic/Sample_GraphicMesh_0.ts b/samples/graphic/Sample_GraphicMesh_0.ts index 18ca0b2d..a8a713a4 100644 --- a/samples/graphic/Sample_GraphicMesh_0.ts +++ b/samples/graphic/Sample_GraphicMesh_0.ts @@ -1,7 +1,8 @@ +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode } from "@orillusion/core"; +import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from '@orillusion/graphic' import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; -import { Stats } from "@orillusion/stats"; export class Sample_GraphicMesh_0 { lightObj3D: Object3D; @@ -9,6 +10,7 @@ export class Sample_GraphicMesh_0 { parts: Object3D[]; width: number; height: number; + graphic3D: Graphic3D; cafe: number = 47; frame: number = 16; @@ -25,8 +27,6 @@ export class Sample_GraphicMesh_0 { Engine3D.setting.render.debug = true; Engine3D.setting.shadow.shadowBound = 5; - - GUIHelp.init(); this.scene = new Scene3D(); @@ -35,97 +35,25 @@ export class Sample_GraphicMesh_0 { sky.enable = false; let camera = CameraUtil.createCamera3DObject(this.scene); camera.perspective(60, Engine3D.aspect, 1, 5000.0); - camera.object3D.addComponent(HoverCameraController).setCamera(30, 0, 120); let view = new View3D(); view.scene = this.scene; view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 3; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0026.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0001.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0009.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0017.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0022.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0025.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0012.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0016.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/star_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0013.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0018.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0023.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0027.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0028.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0035.png") as BitmapTexture2D); - - // texts.push(Engine3D.res.grayTexture); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -136,51 +64,34 @@ export class Sample_GraphicMesh_0 { { this.width = 200; this.height = 100; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; mr.material.blendMode = BlendMode.ADD; - // mr.material.doubleSide = true; mr.material.transparent = true; mr.material.depthWriteEnabled = false; mr.material.useBillboard = true; for (let i = 0; i < this.width * this.height; i++) { - const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); - mr.setTextureID(i, 35); - - - // mr.setTextureID(i, 39); - // mr.setTextureID(i, 18); + // const element = this.parts[i]; + mr.setTextureID(i, 0); } } } update() { if (this.parts) { - let pos = new Vector3(); + // let pos = new Vector3(); for (let i = 0; i < this.parts.length; i++) { const element = this.parts[i]; - let tmp = this.sphericalFibonacci(i, this.parts.length); tmp.scaleBy(Math.sin((i + Time.frame * 0.01)) * 50); - element.transform.localPosition = tmp; } } } - private wave(i: number, pos: Vector3) { - let x = Math.floor(i / this.width); - let z = i % this.height; - pos.set(x, 0, z); - pos.y = Math.sin((x + Time.frame * 0.01) / 8) * 15 * Math.cos((z + Time.frame * 0.01) / 15); - } - public madfrac(A: number, B: number): number { return A * B - Math.floor(A * B); } @@ -197,5 +108,4 @@ export class Sample_GraphicMesh_0 { cosTheta); } - } diff --git a/samples/graphic/Sample_GraphicMesh_1.ts b/samples/graphic/Sample_GraphicMesh_1.ts index 8493975c..0a734841 100644 --- a/samples/graphic/Sample_GraphicMesh_1.ts +++ b/samples/graphic/Sample_GraphicMesh_1.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_1 { lightObj3D: Object3D; @@ -12,6 +13,7 @@ export class Sample_GraphicMesh_1 { cafe: number = 47; frame: number = 16; view: View3D; + graphic3D: Graphic3D; colors: Color[]; @@ -44,90 +46,20 @@ export class Sample_GraphicMesh_1 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } let texts = []; - - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0026.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0001.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0009.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0017.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0022.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0025.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0012.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0016.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/star_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0013.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0018.png") as BitmapTexture2D); texts.push(await Engine3D.res.loadTexture("textures/128/star_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0023.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0027.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0028.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0035.png") as BitmapTexture2D); - - // texts.push(Engine3D.res.grayTexture); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -140,32 +72,22 @@ export class Sample_GraphicMesh_1 { { this.width = 100; this.height = 100; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; mr.material.blendMode = BlendMode.ADD; - // mr.material.doubleSide = true; mr.material.transparent = true; mr.material.depthWriteEnabled = false; mr.material.useBillboard = true; for (let i = 0; i < this.width * this.height; i++) { const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); - mr.setTextureID(i, 39); - // mr.setTextureID(i, 39); - // mr.setTextureID(i, 18); + mr.setTextureID(i, 0); element.transform.scaleX = 2; element.transform.scaleY = 2; element.transform.scaleZ = 2; - - // let c = Color.random(); - // c.a = 0.55; - // this.colors.push(c); } let c1 = new Color(0.65, 0.1, 0.2, 0.15); @@ -195,8 +117,8 @@ export class Sample_GraphicMesh_1 { } for (let i = 0; i < this.tmpArray.length - 1; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), [ + this.graphic3D.Clear(i.toString()); + this.graphic3D.drawLines(i.toString(), [ this.tmpArray[i].transform.worldPosition, this.tmpArray[i + 1].transform.worldPosition, ], @@ -204,14 +126,6 @@ export class Sample_GraphicMesh_1 { } } } - - private wave(i: number, pos: Vector3) { - let x = Math.floor(i / this.width); - let z = i % this.height; - pos.set(x, 0, z); - pos.y = Math.sin((x + Time.frame * 0.01) / 8) * 15 * Math.cos((z + Time.frame * 0.01) / 15); - } - public madfrac(A: number, B: number): number { return A * B - Math.floor(A * B); } diff --git a/samples/graphic/Sample_GraphicMesh_2.ts b/samples/graphic/Sample_GraphicMesh_2.ts index ca9498e0..22b32eae 100644 --- a/samples/graphic/Sample_GraphicMesh_2.ts +++ b/samples/graphic/Sample_GraphicMesh_2.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_2 { lightObj3D: Object3D; @@ -11,7 +12,7 @@ export class Sample_GraphicMesh_2 { height: number; cafe: number = 47; frame: number = 16; - + graphic3D: Graphic3D constructor() { } async run() { @@ -22,9 +23,6 @@ export class Sample_GraphicMesh_2 { await Engine3D.init({ beforeRender: () => this.update() }); Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - GUIHelp.init(); @@ -40,90 +38,20 @@ export class Sample_GraphicMesh_2 { let view = new View3D(); view.scene = this.scene; view.camera = camera; + + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); Engine3D.startRenderView(view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0026.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0001.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0009.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0017.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0022.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0025.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0012.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0016.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/star_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0013.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0018.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0020.png") as BitmapTexture2D); texts.push(await Engine3D.res.loadTexture("textures/128/star_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0023.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0027.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0028.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0035.png") as BitmapTexture2D); - - // texts.push(Engine3D.res.grayTexture); let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -137,31 +65,25 @@ export class Sample_GraphicMesh_2 { { this.width = 200; this.height = 100; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; mr.material.blendMode = BlendMode.ADD; - // mr.material.doubleSide = true; mr.material.transparent = true; mr.material.depthWriteEnabled = false; mr.material.useBillboard = true; for (let i = 0; i < this.width * this.height; i++) { - const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); + // const element = this.parts[i]; mr.setTextureID(i, 35); - // mr.setTextureID(i, 39); - // mr.setTextureID(i, 18); } } } update() { if (this.parts) { - let pos = new Vector3(); + // let pos = new Vector3(); for (let i = 0; i < this.parts.length; i++) { const element = this.parts[i]; @@ -180,13 +102,6 @@ export class Sample_GraphicMesh_2 { } } - private wave(i: number, pos: Vector3) { - let x = Math.floor(i / this.width); - let z = i % this.height; - pos.set(x, 0, z); - pos.y = Math.sin((x + Time.frame * 0.01) / 8) * 15 * Math.cos((z + Time.frame * 0.01) / 15); - } - public madfrac(A: number, B: number): number { return A * B - Math.floor(A * B); } diff --git a/samples/graphic/Sample_GraphicMesh_3.ts b/samples/graphic/Sample_GraphicMesh_3.ts index 26284fc2..6fb4021e 100644 --- a/samples/graphic/Sample_GraphicMesh_3.ts +++ b/samples/graphic/Sample_GraphicMesh_3.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, MeshRenderer, UnLitTexArrayMaterial, BitmapTexture2DArray, Vector3, Graphic3DMesh, Matrix4, Time, PrefabParser } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, MeshRenderer, UnLitTexArrayMaterial, BitmapTexture2DArray, Vector3, Matrix4, Time } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_3 { lightObj3D: Object3D; @@ -11,6 +12,7 @@ export class Sample_GraphicMesh_3 { height: number; cafe: number = 47; frame: number = 16; + graphic3D: Graphic3D; constructor() { } @@ -22,9 +24,6 @@ export class Sample_GraphicMesh_3 { await Engine3D.init({ beforeRender: () => this.update() }); Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - GUIHelp.init(); @@ -41,36 +40,20 @@ export class Sample_GraphicMesh_3 { view.scene = this.scene; view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - - PrefabParser.useWebp = false; let node = await Engine3D.res.loadGltf("gltfs/glb/beer.glb") as Object3D; let geo = node.getComponents(MeshRenderer)[0].geometry; - - // texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); texts.push(Engine3D.res.yellowTexture); let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); @@ -85,22 +68,12 @@ export class Sample_GraphicMesh_3 { { this.width = 100; this.height = 20; - // let geometry = new BoxGeometry(1, 1, 1); - // let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geo, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; - // mr.material.blendMode = BlendMode.ADD; - // mr.material.transparent = true; - // mr.material.depthWriteEnabled = false; - for (let i = 0; i < this.width * this.height; i++) { const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); - // mr.setTextureID(i, 35); mr.setTextureID(i, 0); - // mr.setTextureID(i, 18); let size = Math.random() * 5.0 + 1.0; element.transform.scaleX = size; @@ -112,7 +85,7 @@ export class Sample_GraphicMesh_3 { update() { if (this.parts) { - let pos = new Vector3(); + // let pos = new Vector3(); for (let i = 0; i < this.parts.length; i++) { const element = this.parts[i]; @@ -131,13 +104,6 @@ export class Sample_GraphicMesh_3 { } } - private wave(i: number, pos: Vector3) { - let x = Math.floor(i / this.width); - let z = i % this.height; - pos.set(x, 0, z); - pos.y = Math.sin((x + Time.frame * 0.01) / 8) * 15 * Math.cos((z + Time.frame * 0.01) / 15); - } - public madfrac(A: number, B: number): number { return A * B - Math.floor(A * B); } diff --git a/samples/graphic/Sample_GraphicMesh_4.ts b/samples/graphic/Sample_GraphicMesh_4.ts index ae7424d1..d075efd0 100644 --- a/samples/graphic/Sample_GraphicMesh_4.ts +++ b/samples/graphic/Sample_GraphicMesh_4.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, PrefabParser } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_4 { lightObj3D: Object3D; @@ -11,6 +12,7 @@ export class Sample_GraphicMesh_4 { height: number; cafe: number = 47; frame: number = 16; + graphic3D: Graphic3D; constructor() { } @@ -22,9 +24,6 @@ export class Sample_GraphicMesh_4 { await Engine3D.init({ beforeRender: () => this.update() }); Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - GUIHelp.init(); @@ -41,34 +40,19 @@ export class Sample_GraphicMesh_4 { view.scene = this.scene; view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); - // texts.push(Engine3D.res.yellowTexture); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); diff --git a/samples/graphic/Sample_GraphicMesh_5.ts b/samples/graphic/Sample_GraphicMesh_5.ts index 2d1e48de..938d231a 100644 --- a/samples/graphic/Sample_GraphicMesh_5.ts +++ b/samples/graphic/Sample_GraphicMesh_5.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_5 { lightObj3D: Object3D; @@ -12,6 +13,7 @@ export class Sample_GraphicMesh_5 { cafe: number = 47; frame: number = 16; view: View3D; + graphic3D: Graphic3D; colors: Color[]; @@ -44,90 +46,19 @@ export class Sample_GraphicMesh_5 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/glow_0026.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0001.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0003.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0009.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0017.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0022.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/wave_0025.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0012.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/Vortex_0016.png") as BitmapTexture2D); - - texts.push(await Engine3D.res.loadTexture("textures/128/star_0002.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0004.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0005.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0006.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0007.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0010.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0011.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0013.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0014.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0016.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0018.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0019.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0020.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0021.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0023.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0024.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0025.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0027.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0028.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/128/star_0031.png") as BitmapTexture2D); texts.push(await Engine3D.res.loadTexture("textures/128/star_0035.png") as BitmapTexture2D); - - // texts.push(Engine3D.res.grayTexture); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -140,32 +71,21 @@ export class Sample_GraphicMesh_5 { { this.width = 100; this.height = 100; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; mr.material.blendMode = BlendMode.ADD; - // mr.material.doubleSide = true; mr.material.transparent = true; mr.material.depthWriteEnabled = false; mr.material.useBillboard = true; for (let i = 0; i < this.width * this.height; i++) { const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); mr.setTextureID(i, 39); - // mr.setTextureID(i, 39); - // mr.setTextureID(i, 18); - element.transform.scaleX = 2; element.transform.scaleY = 2; element.transform.scaleZ = 2; - - // let c = Color.random(); - // c.a = 0.55; - // this.colors.push(c); } let c1 = new Color(0.65, 0.1, 0.2, 0.15); @@ -195,8 +115,8 @@ export class Sample_GraphicMesh_5 { } for (let i = 0; i < this.tmpArray.length - 1; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), [ + this.graphic3D.Clear(i.toString()); + this.graphic3D.drawLines(i.toString(), [ this.tmpArray[i].transform.worldPosition, this.tmpArray[i + 1].transform.worldPosition, ], diff --git a/samples/graphic/Sample_GraphicMesh_6.ts b/samples/graphic/Sample_GraphicMesh_6.ts index 53f45ac1..6b270b7f 100644 --- a/samples/graphic/Sample_GraphicMesh_6.ts +++ b/samples/graphic/Sample_GraphicMesh_6.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, BlendMode, Color } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_6 { lightObj3D: Object3D; @@ -12,7 +13,7 @@ export class Sample_GraphicMesh_6 { cafe: number = 47; frame: number = 16; view: View3D; - + graphic3D: Graphic3D; colors: Color[]; constructor() { } @@ -22,7 +23,7 @@ export class Sample_GraphicMesh_6 { Matrix4.maxCount = 500000; Matrix4.allocCount = 500000; - await Engine3D.init({ beforeRender: () => this.update() }); + await Engine3D.init(); Engine3D.setting.render.debug = true; Engine3D.setting.shadow.shadowBound = 5; @@ -44,33 +45,19 @@ export class Sample_GraphicMesh_6 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); await this.initScene(); - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); bitmapTexture2DArray.setTextures(texts); @@ -83,32 +70,22 @@ export class Sample_GraphicMesh_6 { { this.width = 15; this.height = 15; - // let geometry = new BoxGeometry(1, 1, 1); let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); let mr = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); this.parts = mr.object3Ds; mr.material.blendMode = BlendMode.ADD; - // mr.material.doubleSide = true; mr.material.transparent = true; mr.material.depthWriteEnabled = false; mr.material.useBillboard = true; for (let i = 0; i < this.width * this.height; i++) { const element = this.parts[i]; - // mr.setTextureID(i, i % texts.length); - // mr.setTextureID(i, 52); mr.setTextureID(i, 0); - // mr.setTextureID(i, 39); - // mr.setTextureID(i, 18); element.transform.scaleX = 5.5; element.transform.scaleY = 5.5; element.transform.scaleZ = 5.5; - - // let c = Color.random(); - // c.a = 0.55; - // this.colors.push(c); } let c1 = new Color(0.65, 0.1, 0.2, 0.15); @@ -117,16 +94,13 @@ export class Sample_GraphicMesh_6 { this.colors.push(c2); } - this.updateOnce(1000); + this.updateOnce(); } private tmpArray: any[] = []; - update() { - } - - updateOnce(engineFrame: number) { + updateOnce() { if (this.parts) { - let pos = new Vector3(); + // let pos = new Vector3(); this.tmpArray.length = 0; for (let i = 0; i < this.parts.length; i++) { const element = this.parts[i]; @@ -142,8 +116,8 @@ export class Sample_GraphicMesh_6 { } for (let i = 0; i < this.tmpArray.length - 1; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), [ + this.graphic3D.Clear(i.toString()); + this.graphic3D.drawLines(i.toString(), [ Vector3.ZERO, this.tmpArray[i + 1].transform.worldPosition, ], diff --git a/samples/graphic/Sample_GraphicMesh_7.ts b/samples/graphic/Sample_GraphicMesh_7.ts index b753c7ea..29af3ded 100644 --- a/samples/graphic/Sample_GraphicMesh_7.ts +++ b/samples/graphic/Sample_GraphicMesh_7.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, BlendMode, Color, PostProcessingComponent, BloomPost } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_7 { lightObj3D: Object3D; @@ -12,7 +13,7 @@ export class Sample_GraphicMesh_7 { cafe: number = 47; frame: number = 16; view: View3D; - + graphic3D: Graphic3D colors: Color[]; constructor() { } @@ -22,7 +23,7 @@ export class Sample_GraphicMesh_7 { Matrix4.maxCount = 500000; Matrix4.allocCount = 500000; - await Engine3D.init({ beforeRender: () => this.update() }); + await Engine3D.init(); Engine3D.setting.render.debug = true; Engine3D.setting.shadow.shadowBound = 5; @@ -44,6 +45,9 @@ export class Sample_GraphicMesh_7 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); @@ -54,25 +58,9 @@ export class Sample_GraphicMesh_7 { GUIUtil.renderBloom(bloom); await this.initScene(); - - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); @@ -116,14 +104,11 @@ export class Sample_GraphicMesh_7 { this.colors.push(c2); } - this.updateOnce(1000); + this.updateOnce(); } private tmpArray: any[] = []; - update() { - } - - updateOnce(engineFrame: number) { + updateOnce() { if (this.parts) { let pos = new Vector3(); this.tmpArray.length = 0; @@ -136,8 +121,8 @@ export class Sample_GraphicMesh_7 { } for (let i = 0; i < this.tmpArray.length / 3; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), + this.graphic3D.Clear(i.toString()); + this.graphic3D.drawLines(i.toString(), [ this.tmpArray[i * 3 + 0].transform.worldPosition, this.tmpArray[i * 3 + 1].transform.worldPosition, diff --git a/samples/graphic/Sample_GraphicMesh_Color.ts b/samples/graphic/Sample_GraphicMesh_Color.ts deleted file mode 100644 index 4bfc06f7..00000000 --- a/samples/graphic/Sample_GraphicMesh_Color.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, ColorUtil, Graphic3DMeshRenderer } from "@orillusion/core"; -import { GUIUtil } from "@samples/utils/GUIUtil"; -import { Stats } from "@orillusion/stats"; - -export class Sample_GraphicMesh_Color { - private lightObj3D: Object3D; - private scene: Scene3D; - private parts: Object3D[]; - private width: number; - private height: number; - private cafe: number = 47; - private frame: number = 16; - private view: View3D; - private colors: Color[]; - private tmpArray: any[] = []; - - private color1: Color ; - private color2: Color ; - graphicMeshRenderer: Graphic3DMeshRenderer; - - constructor() { } - - async run() { - - Matrix4.maxCount = 500000; - Matrix4.allocCount = 500000; - - await Engine3D.init({ beforeRender: () => this.update() }); - - Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - this.colors = []; - - GUIHelp.init(); - - this.scene = new Scene3D(); - this.scene.addComponent(Stats); - let sky = this.scene.addComponent(AtmosphericComponent); - sky.enable = false; - let camera = CameraUtil.createCamera3DObject(this.scene); - camera.perspective(60, Engine3D.aspect, 1, 5000.0); - - camera.object3D.addComponent(HoverCameraController).setCamera(30, 0, 120); - - this.view = new View3D(); - this.view.scene = this.scene; - this.view.camera = camera; - - Engine3D.startRenderView(this.view); - - GUIUtil.renderDebug(); - - let post = this.scene.addComponent(PostProcessingComponent); - let bloom = post.addPost(BloomPost); - bloom.bloomIntensity = 10.0 - GUIUtil.renderBloom(bloom); - - await this.initScene(); - - sky.relativeTransform = this.lightObj3D.transform; - } - - async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - - let texts = []; - - texts.push(await Engine3D.res.loadTexture("textures/128/star_0008.png") as BitmapTexture2D); - - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); - bitmapTexture2DArray.setTextures(texts); - - let mat = new UnLitTexArrayMaterial(); - mat.baseMap = bitmapTexture2DArray; - mat.name = "LitMaterial"; - - GUIHelp.add(this, "cafe", 0.0, 100.0); - GUIHelp.add(this, "frame", 0.0, 100.0); - { - this.width = 15; - this.height = 15; - let geometry = new PlaneGeometry(1, 1, 1, 1, Vector3.Z_AXIS); - this.graphicMeshRenderer = Graphic3DMesh.draw(this.scene, geometry, bitmapTexture2DArray, this.width * this.height); - this.parts = this.graphicMeshRenderer.object3Ds; - - this.graphicMeshRenderer.material.blendMode = BlendMode.ADD; - this.graphicMeshRenderer.material.transparent = true; - this.graphicMeshRenderer.material.depthWriteEnabled = false; - this.graphicMeshRenderer.material.useBillboard = true; - - for (let i = 0; i < this.width * this.height; i++) { - const element = this.parts[i]; - this.graphicMeshRenderer.setTextureID(i, 0); - element.transform.scaleX = 5.5; - element.transform.scaleY = 5.5; - element.transform.scaleZ = 5.5; - } - - this.color1 = new Color(1.5, 0.1, 0.2, 1.0); - this.color2 = new Color(0.1, 0.1, 4.5, 1.0); - let c2 = new Color(1.0, 1.1, 0.2, 0.65); - this.colors.push(c2); - this.colors.push(c2); - this.colors.push(c2); - this.colors.push(c2); - this.colors.push(c2); - this.colors.push(c2); - } - - this.updateOnce(1000); - } - - update() { - this.updateOnce(Time.frame); - } - - updateOnce(engineFrame: number) { - if (this.parts) { - this.tmpArray.length = 0; - let len = this.parts.length ; - for (let i = 0; i < len; i++) { - const element = this.parts[i]; - let tmp = this.sphericalFibonacci(i, len); - tmp.scaleBy(this.cafe); - element.transform.localPosition = tmp; - this.tmpArray.push(element); - - let c = Color.lerp( Math.sin(engineFrame*0.001 + (i/len)), this.color1,this.color2, Color.COLOR_0 ); - this.graphicMeshRenderer.setBaseColor(i,c); - } - - for (let i = 0; i < this.tmpArray.length - 1; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), [ - Vector3.ZERO, - this.tmpArray[i + 1].transform.worldPosition, - ], - this.colors); - } - } - } - - public madfrac(A: number, B: number): number { - return A * B - Math.floor(A * B); - } - - public sphericalFibonacci(i: number, n: number): Vector3 { - const PHI = Math.sqrt(5.0) * 0.5 + 0.5; - let phi = 2.0 * Math.PI * this.madfrac(i, PHI - 1); - let cosTheta = 1.0 - (2.0 * i + 1.0) * (1.0 / n); - let sinTheta = Math.sqrt(Math.max(Math.min(1.0 - cosTheta * cosTheta, 1.0), 0.0)); - - return new Vector3( - Math.cos(phi) * sinTheta, - Math.sin(phi) * sinTheta, - cosTheta); - } - -} diff --git a/samples/graphic/Sample_GraphicMesh_SpriteSheet.ts b/samples/graphic/Sample_GraphicMesh_SpriteSheet.ts index 03f21098..da73c3ee 100644 --- a/samples/graphic/Sample_GraphicMesh_SpriteSheet.ts +++ b/samples/graphic/Sample_GraphicMesh_SpriteSheet.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, ColorUtil, Graphic3DMeshRenderer, UV } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, Graphic3DMeshRenderer, UV } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_SpriteSheet { private lightObj3D: Object3D; @@ -17,6 +18,7 @@ export class Sample_GraphicMesh_SpriteSheet { private color1: Color; private color2: Color; + graphic3D: Graphic3D; graphicMeshRenderer: Graphic3DMeshRenderer; constructor() { } @@ -48,42 +50,18 @@ export class Sample_GraphicMesh_SpriteSheet { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); - let post = this.scene.addComponent(PostProcessingComponent); - let bloom = post.addPost(BloomPost); - bloom.bloomIntensity = 10.0 - GUIUtil.renderBloom(bloom); - await this.initScene(); - - sky.relativeTransform = this.lightObj3D.transform; } async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - let texts = []; - - // texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0031.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0050.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0036.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0053.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0041.png") as BitmapTexture2D); texts.push(await Engine3D.res.loadTexture("textures/spriteSheet/sequence_0040.png") as BitmapTexture2D); let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); @@ -143,15 +121,13 @@ export class Sample_GraphicMesh_SpriteSheet { tmp.scaleBy(this.cafe); element.transform.localPosition = tmp; this.tmpArray.push(element); - - // let c = Color.lerp(Math.sin(engineFrame * 0.001 + (i / len)), this.color1, this.color2, Color.COLOR_0); - // this.graphicMeshRenderer.setBaseColor(i, c); + // update uv this.graphicMeshRenderer.setUVRect(i, UV.getUVSheet((i / len) * 100 + engineFrame * 0.08, 3, 3)); } for (let i = 0; i < this.tmpArray.length - 1; i++) { - this.view.graphic3D.Clear(i.toString()); - this.view.graphic3D.drawLines(i.toString(), [ + this.graphic3D.Clear(i.toString()); + this.graphic3D.drawLines(i.toString(), [ Vector3.ZERO, this.tmpArray[i + 1].transform.worldPosition, ], diff --git a/samples/graphic/Sample_Shape3D.ts b/samples/graphic/Sample_Shape3D.ts index 2e941853..60a4cb6d 100644 --- a/samples/graphic/Sample_Shape3D.ts +++ b/samples/graphic/Sample_Shape3D.ts @@ -1,7 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Graphic3DMesh, Matrix4, Color, Time, sin, MeshRenderer, Vector2, LineJoin, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, Time, Vector2, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; -import { CircleShape3D, EllipseShape3D, Shape3DMaker, Shape3D } from "@orillusion/graphic"; +import { Graphic3D, CircleShape3D, EllipseShape3D, Shape3DMaker, Shape3D, LineJoin } from "@orillusion/graphic"; import { GUIShape3D } from "@samples/utils/GUIShape3D"; import { GUIUtil } from "@samples/utils/GUIUtil"; @@ -16,6 +16,7 @@ export class Sample_Shape3D { lightObj3D: Object3D; scene: Scene3D; view: View3D; + graphic3D: Graphic3D; async run() { @@ -41,6 +42,9 @@ export class Sample_Shape3D { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); await this.initScene(); diff --git a/samples/graphic/Sample_Shape3DPath2D.ts b/samples/graphic/Sample_Shape3DPath2D.ts index 9014a72f..d5927e25 100644 --- a/samples/graphic/Sample_Shape3DPath2D.ts +++ b/samples/graphic/Sample_Shape3DPath2D.ts @@ -1,7 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Graphic3DMesh, Matrix4, Color, Time, sin, MeshRenderer, Vector2, LineJoin, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; -import { Shape3DMaker, Shape3D } from "@orillusion/graphic"; +import { Shape3DMaker, Shape3D, LineJoin, Graphic3D } from "@orillusion/graphic"; import { GUIShape3D } from "@samples/utils/GUIShape3D"; @@ -39,7 +39,7 @@ export class Sample_Shape3DPath2D { this.view = new View3D(); this.view.scene = this.scene; this.view.camera = camera; - + Engine3D.startRenderView(this.view); await this.initScene(); diff --git a/samples/graphic/Sample_Shape3DPath3D.ts b/samples/graphic/Sample_Shape3DPath3D.ts index 76251268..b752a1b9 100644 --- a/samples/graphic/Sample_Shape3DPath3D.ts +++ b/samples/graphic/Sample_Shape3DPath3D.ts @@ -1,10 +1,9 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, LineJoin, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, BitmapTexture2DArray, BitmapTexture2D, Matrix4, Color, Vector4, Object3DUtil, AxisObject } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; -import { Shape3DMaker, Shape3D } from "@orillusion/graphic"; +import { Shape3DMaker, Shape3D, LineJoin } from "@orillusion/graphic"; import { GUIShape3D } from "@samples/utils/GUIShape3D"; - /** * This example shows how to use Shape3D to draw various different paths in 3D space. * diff --git a/samples/graphic/_Sample_GraphicShape.ts b/samples/graphic/_Sample_GraphicShape.ts deleted file mode 100644 index ba5b3e50..00000000 --- a/samples/graphic/_Sample_GraphicShape.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Graphic3DMesh, Matrix4, BlendMode, Color, Vector4, LineJoin, GeoJsonStruct, GeoJsonUtil, ShapeInfo } from "@orillusion/core"; -import { Stats } from "@orillusion/stats"; - -export class _Sample_GraphicShape { - lightObj3D: Object3D; - scene: Scene3D; - parts: Object3D[]; - width: number; - height: number; - cafe: number = 47; - frame: number = 16; - view: View3D; - - colors: Color[]; - trail3ds: Object3D[][]; - - constructor() { } - - async run() { - - Matrix4.maxCount = 10000; - Matrix4.allocCount = 10000; - - await Engine3D.init({ beforeRender: () => this.update() }); - - Engine3D.setting.render.debug = true; - Engine3D.setting.shadow.shadowBound = 5; - - this.colors = []; - - GUIHelp.init(); - - this.scene = new Scene3D(); - this.scene.addComponent(Stats); - let sky = this.scene.addComponent(AtmosphericComponent); - sky.enable = false; - let camera = CameraUtil.createCamera3DObject(this.scene); - camera.perspective(60, Engine3D.aspect, 1, 5000.0); - - camera.object3D.addComponent(HoverCameraController).setCamera(30, -60, 25); - - this.view = new View3D(); - this.view.scene = this.scene; - this.view.camera = camera; - - Engine3D.startRenderView(this.view); - - // GUIUtil.renderDebug(); - - // let post = this.scene.addComponent(PostProcessingComponent); - // let bloom = post.addPost(BloomPost); - // bloom.bloomIntensity = 1.0 - // GUIUtil.renderBloom(bloom); - - await this.initScene(); - - sky.relativeTransform = this.lightObj3D.transform; - } - - async initScene() { - /******** light *******/ - { - this.lightObj3D = new Object3D(); - this.lightObj3D.rotationX = 21; - this.lightObj3D.rotationY = 108; - this.lightObj3D.rotationZ = 10; - let directLight = this.lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = false; - directLight.intensity = 10; - // GUIUtil.renderDirLight(directLight); - this.scene.addChild(this.lightObj3D); - } - - // let geoJsonData = await Engine3D.res.loadJSON("gis/geojson/pudong.geoJson") as GeoJsonStruct; - let geoJsonData = await Engine3D.res.loadJSON("gis/geojson/100000.geoJson") as GeoJsonStruct; - - let texts = []; - // texts.push(await Engine3D.res.loadTexture("textures/line.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/line2.png") as BitmapTexture2D); - texts.push(await Engine3D.res.loadTexture("textures/line3.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/line4.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/grid.jpg") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/frame64.png") as BitmapTexture2D); - // texts.push(await Engine3D.res.loadTexture("textures/line_001064.png") as BitmapTexture2D); - - - - let bitmapTexture2DArray = new BitmapTexture2DArray(texts[0].width, texts[0].height, texts.length); - bitmapTexture2DArray.setTextures(texts); - - let mat = new UnLitTexArrayMaterial(); - mat.baseMap = bitmapTexture2DArray; - mat.name = "LitMaterial"; - mat.blendMode = BlendMode.SOFT_ADD; - - let mat4 = new Matrix4(); - mat4.rawData.set([]); - - GUIHelp.add(this, "cafe", 0.0, 100.0); - GUIHelp.add(this, "frame", 0.0, 100.0); - { - this.width = 1; - this.height = 1; - - let neg = true; - - let lineArray = GeoJsonUtil.getPath(geoJsonData); - let mr = Graphic3DMesh.drawShape(`path_geojson`, this.scene, bitmapTexture2DArray); - for (let i = 0; i < this.width * this.height; i++) { - mr.setTextureID(i, Math.floor(Math.random() * texts.length)); - } - - // for (let ii = 0; ii < 1; ii++) { - for (let ii = 0; ii < lineArray.length; ii++) { - this.parts = mr.object3Ds; - - let shapeInfo = new ShapeInfo(); - shapeInfo.shapeType = 3; - shapeInfo.lineJoin = LineJoin.bevel; - shapeInfo.width = 0.25; - shapeInfo.uScale = 1; - shapeInfo.vScale = 10; - shapeInfo.uSpeed = 0; - shapeInfo.vSpeed = 6; - mr.setShape(ii, shapeInfo); - let tmp = []; - for (let i = 0; i < lineArray[ii].length; i++) { - let p = lineArray[ii][i]; - let newPos = new Vector4(); - newPos.set(p.x - 121, p.y, p.z - 31, 0.0); - tmp.push(newPos); - newPos.multiplyScalar(25); - mr.shapes[ii].paths.push(newPos); - } - - // mr.shapes[ii].paths.push(new Vector4(0.0, 0.0, 0.0)); - // mr.shapes[ii].paths.push(new Vector4(1.0, 0.0, 0.0)); - // mr.shapes[ii].paths.push(new Vector4(2.0, 0.0, 5.0)); - // mr.shapes[ii].paths.push(new Vector4(4.0, 0.0, 2.0)); - // mr.shapes[ii].paths.push(new Vector4(8.0, 0.0, 2.0)); - // mr.shapes[ii].paths.push(new Vector4(10.0, 0.0, 5.0)); - console.log(`path${ii}`, tmp.length); - // Engine3D.views[0].graphic3D.drawLines(`path${ii}`, tmp); - } - mr.updateShape(); - // Engine3D.views[0].graphic3D.drawFillCircle(`zero`, Vector3.ZERO, 0.5); - - } - } - - update() { - } -} diff --git a/samples/graphic/_Sample_GraphicTrailing.ts b/samples/graphic/_Sample_GraphicTrailing.ts index 488fa6ca..6771f136 100644 --- a/samples/graphic/_Sample_GraphicTrailing.ts +++ b/samples/graphic/_Sample_GraphicTrailing.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, TrailGeometry, AnimationCurve, Keyframe, AnimationCurveT, KeyframeT, DepthOfFieldPost } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Vector3, Matrix4, BlendMode, Color, PostProcessingComponent, BloomPost, AnimationCurve, Keyframe } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_Trailing { lightObj3D: Object3D; @@ -12,6 +13,7 @@ export class Sample_GraphicMesh_Trailing { cafe: number = 47; frame: number = 16; view: View3D; + graphic3D: Graphic3D; colors: Color[]; trail3ds: Object3D[]; @@ -45,24 +47,19 @@ export class Sample_GraphicMesh_Trailing { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); let post = this.scene.addComponent(PostProcessingComponent); - // let DOFPost = post.addPost(DepthOfFieldPost) - // DOFPost.near = 0 - // DOFPost.far = 150 - // DOFPost.pixelOffset = 2 - // GUIUtil.renderDepthOfField(DOFPost); - let bloom = post.addPost(BloomPost); bloom.bloomIntensity = 10.0 GUIUtil.renderBloom(bloom); await this.initScene(); - - // sky.relativeTransform = this.lightObj3D.transform; } async initScene() { diff --git a/samples/graphic/_Sample_GraphicTrailing2.ts b/samples/graphic/_Sample_GraphicTrailing2.ts index 602a614a..2c94aac2 100644 --- a/samples/graphic/_Sample_GraphicTrailing2.ts +++ b/samples/graphic/_Sample_GraphicTrailing2.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, TrailGeometry, AnimationCurve, Keyframe, AnimationCurveT, KeyframeT, DepthOfFieldPost, Quaternion } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Vector3, Matrix4, BlendMode, Color, PostProcessingComponent, BloomPost } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D, Graphic3DMesh } from "@orillusion/graphic"; export class Sample_GraphicMesh_Trailing2 { lightObj3D: Object3D; @@ -12,7 +13,7 @@ export class Sample_GraphicMesh_Trailing2 { cafe: number = 47; frame: number = 16; view: View3D; - + graphic3D: Graphic3D; colors: Color[]; trail3ds: Object3D[]; @@ -45,24 +46,19 @@ export class Sample_GraphicMesh_Trailing2 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); let post = this.scene.addComponent(PostProcessingComponent); - // let DOFPost = post.addPost(DepthOfFieldPost) - // DOFPost.near = 0 - // DOFPost.far = 150 - // DOFPost.pixelOffset = 2 - // GUIUtil.renderDepthOfField(DOFPost); - let bloom = post.addPost(BloomPost); bloom.bloomIntensity = 10.0 GUIUtil.renderBloom(bloom); await this.initScene(); - - // sky.relativeTransform = this.lightObj3D.transform; } async initScene() { diff --git a/samples/graphic/_Sample_GraphicTrailing3.ts b/samples/graphic/_Sample_GraphicTrailing3.ts index 0ea0c634..20b763b7 100644 --- a/samples/graphic/_Sample_GraphicTrailing3.ts +++ b/samples/graphic/_Sample_GraphicTrailing3.ts @@ -1,7 +1,8 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, SphereGeometry, VirtualTexture, GPUTextureFormat, UnLitMaterial, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, PlaneGeometry, Vector3, Graphic3DMesh, Matrix4, Time, BlendMode, Color, PostProcessingComponent, BloomPost, TrailGeometry, AnimationCurve, Keyframe, AnimationCurveT, KeyframeT, DepthOfFieldPost, Quaternion } from "@orillusion/core"; +import { Object3D, Scene3D, Engine3D, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil, UnLitTexArrayMaterial, BitmapTexture2DArray, BitmapTexture2D, Vector3, Matrix4, BlendMode, Color, PostProcessingComponent, BloomPost } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; import { Stats } from "@orillusion/stats"; +import { Graphic3D } from "@orillusion/graphic"; export class Sample_GraphicTrailing3 { lightObj3D: Object3D; @@ -12,6 +13,7 @@ export class Sample_GraphicTrailing3 { cafe: number = 47; frame: number = 16; view: View3D; + graphic3D: Graphic3D; colors: Color[]; trail3ds: Object3D[]; @@ -45,24 +47,19 @@ export class Sample_GraphicTrailing3 { this.view.scene = this.scene; this.view.camera = camera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + Engine3D.startRenderView(this.view); GUIUtil.renderDebug(); let post = this.scene.addComponent(PostProcessingComponent); - // let DOFPost = post.addPost(DepthOfFieldPost) - // DOFPost.near = 0 - // DOFPost.far = 150 - // DOFPost.pixelOffset = 2 - // GUIUtil.renderDepthOfField(DOFPost); - let bloom = post.addPost(BloomPost); bloom.bloomIntensity = 10.0 GUIUtil.renderBloom(bloom); await this.initScene(); - - // sky.relativeTransform = this.lightObj3D.transform; } async initScene() { diff --git a/samples/lights/Sample_CSM.ts b/samples/lights/Sample_CSM.ts index ea95f85f..10c5a621 100644 --- a/samples/lights/Sample_CSM.ts +++ b/samples/lights/Sample_CSM.ts @@ -1,6 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { Scene3D, HoverCameraController, Engine3D, AtmosphericComponent, Object3D, Camera3D, Vector3, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, CameraUtil, SphereGeometry, Color, Object3DUtil, BlendMode } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; +import { Graphic3D } from "@orillusion/graphic"; //sample of csm class Sample_CSM { @@ -9,6 +10,7 @@ class Sample_CSM { light: DirectLight; boxRenderer: MeshRenderer; viewCamera: Camera3D; + graphic3D: Graphic3D; async run() { Engine3D.setting.shadow.autoUpdate = true; Engine3D.setting.shadow.shadowSize = 2048; @@ -37,6 +39,9 @@ class Sample_CSM { this.view = view; this.viewCamera = mainCamera; + this.graphic3D = new Graphic3D(); + this.scene.addChild(this.graphic3D); + mainCamera.enableCSM = true; GUIHelp.addFolder('CSM') GUIHelp.add(mainCamera, 'enableCSM'); @@ -139,7 +144,7 @@ class Sample_CSM { this._shadowPos.copy(light.direction).normalize(viewCamera.far); csmBound.center.add(this._shadowPos, this._shadowCameraTarget); csmBound.center.subtract(this._shadowPos, this._shadowPos); - view.graphic3D.drawLines('shadowLine', [this._shadowPos, this._shadowCameraTarget], new Color(1, 1, 0, 1)); + this.graphic3D.drawLines('shadowLine', [this._shadowPos, this._shadowCameraTarget], new Color(1, 1, 0, 1)); } } diff --git a/samples/octree/Sample_OctTreeBox.ts b/samples/octree/Sample_OctTreeBox.ts index a053da8e..1981efb7 100644 --- a/samples/octree/Sample_OctTreeBox.ts +++ b/samples/octree/Sample_OctTreeBox.ts @@ -1,12 +1,12 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; -import { BoundingBox, BoxGeometry, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Object3DUtil, PointerEvent3D, Time, Vector3, View3D, } from '@orillusion/core'; +import { BoundingBox, BoxGeometry, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Octree, OctreeEntity, Vector3, View3D, } from '@orillusion/core'; import { createExampleScene, createSceneParam } from '@samples/utils/ExampleScene'; -import { OctreeEntity } from '../../src/core/tree/octree/OctreeEntity'; -import { Octree } from '../../src/core/tree/octree/Octree'; +import { Graphic3D } from '@orillusion/graphic'; // A sample to use octTree export class Sample_OctTreeBox { view: View3D; + graphic3D: Graphic3D; entities: OctreeEntity[] = []; tree: Octree; red = new Color(1, 0, 0, 1); @@ -31,6 +31,9 @@ export class Sample_OctTreeBox { this.view = exampleScene.view; + this.graphic3D = new Graphic3D(); + exampleScene.scene.addChild(this.graphic3D); + let box: BoundingBox = new BoundingBox(); box.setFromMinMax(new Vector3(-100, -100, -100), new Vector3(100, 100, 100)); this.tree = new Octree(box); @@ -105,7 +108,7 @@ export class Sample_OctTreeBox { for (let item of this.entities) { item.renderer.enable = retBoolean[item.uuid]; } - this.view.graphic3D.drawBoundingBox('pick', this.movingBox, this.green); + this.graphic3D.drawBoundingBox('pick', this.movingBox, this.green); } loop() { diff --git a/samples/octree/Sample_OctTreeFrustum.ts b/samples/octree/Sample_OctTreeFrustum.ts index 9dff8c23..39c01f01 100644 --- a/samples/octree/Sample_OctTreeFrustum.ts +++ b/samples/octree/Sample_OctTreeFrustum.ts @@ -1,14 +1,14 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; -import { BoundingBox, BoxGeometry, Camera3D, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Object3DUtil, PointerEvent3D, Time, Vector3, View3D, } from '@orillusion/core'; +import { BoundingBox, BoxGeometry, Camera3D, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Object3DUtil, Octree, OctreeEntity, PointerEvent3D, Time, Vector3, View3D, } from '@orillusion/core'; import { createExampleScene, createSceneParam } from '@samples/utils/ExampleScene'; -import { OctreeEntity } from '../../src/core/tree/octree/OctreeEntity'; -import { Octree } from '../../src/core/tree/octree/Octree'; import { GUIUtil } from '@samples/utils/GUIUtil'; import { Stats } from '@orillusion/stats'; +import { Graphic3D } from '@orillusion/graphic'; // A sample to use octTree export class Sample_OctTreeFrustum { view: View3D; + graphic3D: Graphic3D; entities: OctreeEntity[] = []; tree: Octree; red = new Color(1, 0, 0, 1); @@ -36,6 +36,9 @@ export class Sample_OctTreeFrustum { this.view = exampleScene.view; this.view.scene.addComponent(Stats); + this.graphic3D = new Graphic3D(); + this.view.scene.addChild(this.graphic3D); + let box: BoundingBox = new BoundingBox(); box.setFromCenterAndSize(new Vector3(), new Vector3(1000, 1000, 1000)); this.tree = new Octree(box); @@ -82,8 +85,8 @@ export class Sample_OctTreeFrustum { private queryResult: OctreeEntity[] = []; private octreeTest() { - this.view.graphic3D.ClearAll(); - this.view.graphic3D.drawCameraFrustum(this.camera, this.green); + this.graphic3D.ClearAll(); + this.graphic3D.drawCameraFrustum(this.camera, this.green); // this.view.graphic3D.drawBoundingBox('box', this.camera.frustum.boudingBox, this.red); // this.camera.frustum.csm.name = 'sdfdf'; // for (let block of this.camera.frustum.csm.children) { diff --git a/samples/octree/Sample_OctTreeRay.ts b/samples/octree/Sample_OctTreeRay.ts index 9da97b8d..832a0ae8 100644 --- a/samples/octree/Sample_OctTreeRay.ts +++ b/samples/octree/Sample_OctTreeRay.ts @@ -1,12 +1,12 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; -import { BoundingBox, BoxGeometry, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Object3DUtil, PointerEvent3D, Time, Vector3, View3D, } from '@orillusion/core'; +import { BoundingBox, BoxGeometry, Color, Engine3D, LitMaterial, MeshRenderer, Object3D, Object3DUtil, Octree, OctreeEntity, PointerEvent3D, Time, Vector3, View3D, } from '@orillusion/core'; import { createExampleScene, createSceneParam } from '@samples/utils/ExampleScene'; -import { OctreeEntity } from '../../src/core/tree/octree/OctreeEntity'; -import { Octree } from '../../src/core/tree/octree/Octree'; +import { Graphic3D } from '@orillusion/graphic'; // A sample to use octTree export class Sample_OctTreeRay { view: View3D; + graphic3D: Graphic3D; entities: OctreeEntity[] = []; tree: Octree; red = new Color(1, 0, 0, 1); @@ -31,6 +31,8 @@ export class Sample_OctTreeRay { Engine3D.getRenderJob(exampleScene.view); this.view = exampleScene.view; + this.graphic3D = new Graphic3D(); + this.view.scene.addChild(this.graphic3D); let box: BoundingBox = new BoundingBox(); box.setFromCenterAndSize(new Vector3(), new Vector3(400, 400, 400)); @@ -79,7 +81,7 @@ export class Sample_OctTreeRay { this.tree.rayCasts(ray, this.queryResult); let time: number = Date.now() - now; console.log('time: ' + time + ' count: ', this.queryResult.length); - this.view.graphic3D.ClearAll(); + this.graphic3D.ClearAll(); let retBoolean = {}; let boundList = {}; @@ -96,7 +98,7 @@ export class Sample_OctTreeRay { //show box for (let key in boundList) { let tree = boundList[key]; - this.view.graphic3D.drawBoundingBox(key, tree.box, this.green); + this.graphic3D.drawBoundingBox(key, tree.box, this.green); } } diff --git a/samples/utils/GUIShape3D.ts b/samples/utils/GUIShape3D.ts index da3a5100..469666a6 100644 --- a/samples/utils/GUIShape3D.ts +++ b/samples/utils/GUIShape3D.ts @@ -1,6 +1,6 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { CircleShape3D, CurveShape3D, EllipseShape3D, QuadraticCurveShape3D, RoundRectShape3D, LineShape3D, Shape3D, CircleArcType, Path2DShape3D, Path3DShape3D } from "@orillusion/graphic"; -import { LineJoin } from "@orillusion/core"; +import { LineJoin } from "@orillusion/graphic"; import { GUIUtil } from "./GUIUtil"; export class GUIShape3D { diff --git a/src/components/lights/Light.ts b/src/components/lights/Light.ts index 98c37489..3a67cca3 100644 --- a/src/components/lights/Light.ts +++ b/src/components/lights/Light.ts @@ -109,7 +109,10 @@ export class Light extends LightBase { } public onGraphic(view?: View3D): void { - let custom = view.graphic3D.createCustomShape( + let graphic3D = view.scene.getChildByName('graphic3D') + if(!graphic3D) + return + let custom = graphic3D.createCustomShape( `PointLight_${this.object3D.instanceID}`, this.transform, ); diff --git a/src/components/lights/PointLight.ts b/src/components/lights/PointLight.ts index 51a97899..aeb2284e 100644 --- a/src/components/lights/PointLight.ts +++ b/src/components/lights/PointLight.ts @@ -108,7 +108,10 @@ export class PointLight extends LightBase { } public onGraphic(view?: View3D): void { - // let custom = view.graphic3D.createCustomShape( + // let graphic3D = view.scene.getChildByName('graphic3D') + // if(!graphic3D) + // return + // let custom = graphic3D.createCustomShape( // `PointLight_${this.object3D.instanceID}`, // this.transform, // ); diff --git a/src/components/lights/SpotLight.ts b/src/components/lights/SpotLight.ts index fb63c999..0a8506a2 100644 --- a/src/components/lights/SpotLight.ts +++ b/src/components/lights/SpotLight.ts @@ -124,7 +124,10 @@ export class SpotLight extends LightBase { } public onGraphic(view: View3D) { - // let custom = view.graphic3D.createCustomShape( + // let graphic3D = view.scene.getChildByName('graphic3D') + // if(!graphic3D) + // return + // let custom = graphic3D.createCustomShape( // `SpotLight_${this.object3D.instanceID}`, // this.transform, // ); diff --git a/src/components/renderer/MeshRenderer.ts b/src/components/renderer/MeshRenderer.ts index 8208f7eb..23ab95ab 100644 --- a/src/components/renderer/MeshRenderer.ts +++ b/src/components/renderer/MeshRenderer.ts @@ -159,7 +159,10 @@ export class MeshRenderer extends RenderNode { } // public onGraphic(view?: View3D) { + // let graphic3D = view.scene.getChildByName('graphic3D') + // if(!graphic3D) + // return // if (this._geometry) - // view.graphic3D.drawMeshWireframe(this._geometry.instanceID, this._geometry, this.transform, Color.COLOR_RED); + // graphic3D.drawMeshWireframe(this._geometry.instanceID, this._geometry, this.transform, Color.COLOR_RED); // } } diff --git a/src/core/View3D.ts b/src/core/View3D.ts index c90ec262..01cc6c8b 100644 --- a/src/core/View3D.ts +++ b/src/core/View3D.ts @@ -3,7 +3,6 @@ import { GUIPick } from "../components/gui/GUIPick"; import { GUICanvas } from "../components/gui/core/GUICanvas"; import { CEventListener } from "../event/CEventListener"; import { ShadowLightsCollect } from "../gfx/renderJob/collect/ShadowLightsCollect"; -import { Graphic3D } from "../gfx/renderJob/passRenderer/graphic/Graphic3DRender"; import { PickFire } from "../io/PickFire"; import { Vector4 } from "../math/Vector4"; import { Camera3D } from "./Camera3D"; @@ -19,16 +18,10 @@ export class View3D extends CEventListener { public guiPick: GUIPick; public readonly canvasList: GUICanvas[]; - /** - * Graphics renderers (lines, rectangles, etc.) - */ - public graphic3D: Graphic3D; - constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) { super(); this.canvasList = []; this._viewPort = new Vector4(x, y, width, height); - this.graphic3D = new Graphic3D(); } public get enable(): boolean { @@ -61,9 +54,6 @@ export class View3D extends CEventListener { ShadowLightsCollect.createBuffer(this); - if (this.graphic3D) - value.addChild(this.graphic3D); - if (value) { this.canvasList.forEach(canvas => { canvas && value.addChild(canvas.object3D); @@ -114,8 +104,4 @@ export class View3D extends CEventListener { } } - // public get graphic3D(): Graphic3D { - // return Engine3D.getRenderJob(this).graphic3D; - // } - } \ No newline at end of file diff --git a/src/gfx/renderJob/collect/EntityCollect.ts b/src/gfx/renderJob/collect/EntityCollect.ts index 8ce2ea57..c43d80e1 100644 --- a/src/gfx/renderJob/collect/EntityCollect.ts +++ b/src/gfx/renderJob/collect/EntityCollect.ts @@ -13,7 +13,7 @@ import { Vector3 } from '../../../math/Vector3'; import { zSorterUtil } from '../../../util/ZSorterUtil'; import { RenderLayerUtil, RenderLayer } from '../config/RenderLayer'; import { Probe } from '../passRenderer/ddgi/Probe'; -import { Graphic3DBatchRenderer } from '../passRenderer/graphic/Graphic3DBatchRenderer'; +// import { Graphic3DBatchRenderer } from '../passRenderer/graphic/Graphic3DBatchRenderer'; import { RendererMask } from '../passRenderer/state/RendererMask'; import { CollectInfo } from './CollectInfo'; import { EntityBatchCollect } from './EntityBatchCollect'; @@ -35,7 +35,7 @@ export class EntityCollect { private _octreeRenderNodes: Map; private _reflections: Map; - private _graphics: Graphic3DBatchRenderer[]; + private _graphics: RenderNode[]; private _op_renderGroup: Map; private _tr_renderGroup: Map; @@ -117,7 +117,7 @@ export class EntityCollect { if (!maps.includes(renderNode as Reflection)) { maps.push(renderNode as Reflection); } - } else if (renderNode instanceof Graphic3DBatchRenderer) { + } else if (renderNode.hasMask(RendererMask.Graphic3D)) { if (this._graphics.indexOf(renderNode) == -1) { this._graphics.push(renderNode); } @@ -315,7 +315,7 @@ export class EntityCollect { return this._tr_renderGroup.get(scene); } - public getGraphicList(): Graphic3DBatchRenderer[] { + public getGraphicList(): RenderNode[] { return this._graphics; } diff --git a/src/gfx/renderJob/passRenderer/state/RendererMask.ts b/src/gfx/renderJob/passRenderer/state/RendererMask.ts index 702e6b64..164e00a6 100644 --- a/src/gfx/renderJob/passRenderer/state/RendererMask.ts +++ b/src/gfx/renderJob/passRenderer/state/RendererMask.ts @@ -13,6 +13,7 @@ export enum RendererMask { UI = 1 << 7, Reflection = 1 << 8, ReflectionDebug = 1 << 9, + Graphic3D = 1 << 10 } /** diff --git a/src/index.ts b/src/index.ts index 03684f4d..18fa8bea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,15 +67,6 @@ export * from "./assets/shader/env/ReflectionCG" export * from "./assets/shader/glsl/Quad_glsl" export * from "./assets/shader/glsl/Sky_glsl" export * from "./assets/shader/glsl/post/LUT_glsl" -export * from "./assets/shader/graphic/Graphic3DShader" -export * from "./assets/shader/graphic/GraphicDecCompute" -export * from "./assets/shader/graphic/GraphicDynamicCompute" -export * from "./assets/shader/graphic/GraphicFaceComput2" -export * from "./assets/shader/graphic/GraphicFaceCompute" -export * from "./assets/shader/graphic/GraphicFaceCompute3" -export * from "./assets/shader/graphic/GraphicLineCompute" -export * from "./assets/shader/graphic/GraphicTrailCompute" -export * from "./assets/shader/graphic/GraphicTrailCompute2" export * from "./assets/shader/lighting/BRDF_frag" export * from "./assets/shader/lighting/BsDF_frag" export * from "./assets/shader/lighting/BxDF_frag" @@ -360,20 +351,6 @@ export * from "./gfx/renderJob/passRenderer/ddgi/DDGILightingPass" export * from "./gfx/renderJob/passRenderer/ddgi/DDGIMultiBouncePass" export * from "./gfx/renderJob/passRenderer/ddgi/DDGIProbeRenderer" export * from "./gfx/renderJob/passRenderer/ddgi/Probe" -export * from "./gfx/renderJob/passRenderer/graphic/Graphic3DBatchRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/Graphic3DFillRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/Graphic3DFixedRenderMaterial" -export * from "./gfx/renderJob/passRenderer/graphic/Graphic3DLineBatchRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/Graphic3DRender" -export * from "./gfx/renderJob/passRenderer/graphic/Graphics3DShape" -export * from "./gfx/renderJob/passRenderer/graphic/new/DynamicDrawStruct" -export * from "./gfx/renderJob/passRenderer/graphic/new/DynamicFaceRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/new/Float32ArrayUtil" -export * from "./gfx/renderJob/passRenderer/graphic/new/Graphic3DFaceRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/new/Graphic3DMesh" -export * from "./gfx/renderJob/passRenderer/graphic/new/Graphic3DMeshRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/new/Graphic3DRibbonRenderer" -export * from "./gfx/renderJob/passRenderer/graphic/new/ShapeInfo" export * from "./gfx/renderJob/passRenderer/post/PostRenderer" export * from "./gfx/renderJob/passRenderer/preDepth/PreDepthPassRenderer" export * from "./gfx/renderJob/passRenderer/preDepth/ZCullingCompute" diff --git a/src/loader/parser/prefab/prefabData/APatch.ts b/src/loader/parser/prefab/prefabData/APatch.ts index f55bee6f..d834ed8f 100644 --- a/src/loader/parser/prefab/prefabData/APatch.ts +++ b/src/loader/parser/prefab/prefabData/APatch.ts @@ -16,19 +16,19 @@ export class APatch extends ComponentBase { public aPaths: number[]; public onGraphic(view?: View3D) { - return; - - // for (let i = this.size.x ; i > 0 ; i--) { - for (let i = 0; i < this.size.x; i++) { - for (let j = 0; j < this.size.y; j++) { - let index = j * this.size.x + (i); - let data = this.aPaths[index]; - let color = this.colors[data] ; + // let graphic3D = view.scene.getChildByName('graphic3D') + // if(!graphic3D) + // return + // // for (let i = this.size.x ; i > 0 ; i--) { + // for (let i = 0; i < this.size.x; i++) { + // for (let j = 0; j < this.size.y; j++) { + // let index = j * this.size.x + (i); + // let data = this.aPaths[index]; + // let color = this.colors[data] ; - let pos = new Vector3(-i * this.blockSize + this.object3D.x , 0 + this.object3D.y , j * this.blockSize+ this.object3D.z); - view.graphic3D.drawFillRect(`${i}-${j}` , pos , this.blockSize , this.blockSize, color ); - } - } - + // let pos = new Vector3(-i * this.blockSize + this.object3D.x , 0 + this.object3D.y , j * this.blockSize+ this.object3D.z); + // graphic3D.drawFillRect(`${i}-${j}` , pos , this.blockSize , this.blockSize, color ); + // } + // } } } \ No newline at end of file From 2a446a24ee3844f0052f71e80a67d1cf6a7b3c9b Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Tue, 23 Jul 2024 18:34:53 +0800 Subject: [PATCH 04/25] chore: update graphic tsconfig --- .../graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts | 4 +--- packages/graphic/renderer/graphic3d/ShapeInfo.ts | 4 +--- packages/graphic/tsconfig.json | 3 +-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts b/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts index 03f37d35..cbe4a6dd 100644 --- a/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts +++ b/packages/graphic/renderer/graphic3d/Graphic3DRibbonRenderer.ts @@ -1,4 +1,4 @@ -import { BitmapTexture2DArray, Color, ComputeShader, GeometryBase, GeometryUtil, GlobalBindGroup, GPUContext, MeshRenderer, NonSerialize, Object3D, StorageGPUBuffer, Struct, StructStorageGPUBuffer, TrailGeometry, UnLitTexArrayMaterial, Vector2, Vector4, View3D } from "@orillusion/core"; +import { BitmapTexture2DArray, Color, ComputeShader, GeometryBase, GeometryUtil, GlobalBindGroup, GPUContext, MeshRenderer, Object3D, StorageGPUBuffer, Struct, StructStorageGPUBuffer, TrailGeometry, UnLitTexArrayMaterial, Vector2, Vector4, View3D } from "@orillusion/core"; import { graphicTrailCompute } from '../../compute/graphic3d/GraphicTrailCompute' export enum FaceMode { @@ -18,8 +18,6 @@ export class RibbonStruct extends Struct { public faceMode: number = FaceMode.FaceToCamera; public up: Vector4 = new Vector4(0, 1, 0); public ids: Float32Array = new Float32Array(Graphic3DRibbonRenderer.maxRibbonSegment); - - @NonSerialize public ribbonPoint: Object3D[] = []; } diff --git a/packages/graphic/renderer/graphic3d/ShapeInfo.ts b/packages/graphic/renderer/graphic3d/ShapeInfo.ts index 3fd07745..96276923 100644 --- a/packages/graphic/renderer/graphic3d/ShapeInfo.ts +++ b/packages/graphic/renderer/graphic3d/ShapeInfo.ts @@ -1,4 +1,4 @@ -import { NonSerialize, Struct, Vector4 } from "@orillusion/core"; +import { Struct, Vector4 } from "@orillusion/core"; export class ShapeInfo extends Struct { public shapeIndex: number = 0; //face,poly,line,cycle,rectangle,box,sphere @@ -14,7 +14,5 @@ export class ShapeInfo extends Struct { public endPath: number = 0; public uSpeed: number = 0; public vSpeed: number = 0; - - @NonSerialize public paths: Vector4[] = []; } \ No newline at end of file diff --git a/packages/graphic/tsconfig.json b/packages/graphic/tsconfig.json index 69d04abd..99cc83e4 100644 --- a/packages/graphic/tsconfig.json +++ b/packages/graphic/tsconfig.json @@ -25,7 +25,6 @@ "paths": { "@orillusion/core": ["../../src"], "@orillusion/*": ["../*"] - }, - "experimentalDecorators": true + } } } \ No newline at end of file From fbc00a6c58cd4ad8ffd0dbb8c95f47550445e313 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Tue, 23 Jul 2024 21:50:41 +0800 Subject: [PATCH 05/25] chore: update ssr sample --- package.json | 2 +- samples/post/Sample_SSR.ts | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 108885a2..dd33a635 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/core", - "version": "0.8.2", + "version": "0.8.3-dev.1", "author": "Orillusion", "description": "Orillusion WebGPU Engine", "type": "module", diff --git a/samples/post/Sample_SSR.ts b/samples/post/Sample_SSR.ts index 4527b3d6..9ba24bc7 100644 --- a/samples/post/Sample_SSR.ts +++ b/samples/post/Sample_SSR.ts @@ -65,8 +65,9 @@ export class Sample_SSR { if (mr && mr.material) { if (mr.material.name == 'ToyCar') { let mat = mr.material as LitMaterial; - mat.metallic = 0.5; - mat.clearcoatFactor = 0.25; + mat.metallic = 0.9; + mat.roughness = 0.1; + mat.clearcoatFactor = 0.5; } } } @@ -77,14 +78,10 @@ export class Sample_SSR { private sphere: Object3D private async createPlane(scene: Scene3D) { - let mat = new LitMaterial() - mat.roughness = 0.2 - mat.metallic = 0.5 - { let floorMaterial = new LitMaterial() - floorMaterial.roughness = 0.12 - floorMaterial.metallic = 0.5 + floorMaterial.roughness = 0.1 + floorMaterial.metallic = 1.0 let planeGeometry = new PlaneGeometry(200, 200) let floor: Object3D = new Object3D() @@ -93,11 +90,16 @@ export class Sample_SSR { mr.geometry = planeGeometry scene.addChild(floor) + GUIHelp.addFolder('floor') GUIHelp.add(floorMaterial, 'roughness', 0.01, 1, 0.01) GUIHelp.add(floorMaterial, 'metallic', 0, 1, 0.01) + GUIHelp.endFolder() } { + let mat = new LitMaterial() + mat.roughness = 0.1 + mat.metallic = 0.9 let sphereGeometry = new SphereGeometry(10, 50, 50) let obj: Object3D = new Object3D() let mr = obj.addComponent(MeshRenderer) @@ -107,6 +109,11 @@ export class Sample_SSR { obj.y = 10 scene.addChild(obj) this.sphere = obj + + GUIHelp.addFolder('sphere') + GUIHelp.add(mat, 'roughness', 0.01, 1, 0.01) + GUIHelp.add(mat, 'metallic', 0, 1, 0.01) + GUIHelp.endFolder() } { From 5ad10a21c20b873610cd93b116cecd4b61ea7268 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 24 Jul 2024 17:39:56 +0800 Subject: [PATCH 06/25] chore: fix compute samples --- samples/compute/Sample_Cloth.ts | 7 ++----- samples/compute/Sample_FlowImg.ts | 4 ++-- samples/compute/cloth/ClothSimulator.ts | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/samples/compute/Sample_Cloth.ts b/samples/compute/Sample_Cloth.ts index 766275a5..ea3b2fdd 100644 --- a/samples/compute/Sample_Cloth.ts +++ b/samples/compute/Sample_Cloth.ts @@ -7,12 +7,9 @@ export class Demo_Cloth { } async run() { - - Engine3D.setting.shadow.autoUpdate = true; - Engine3D.setting.shadow.updateFrameRate = 1; - Engine3D.setting.shadow.shadowBound = 10; + Engine3D.setting.shadow.shadowBound = 5; Engine3D.setting.shadow.shadowSize = 2048; - Engine3D.setting.shadow.shadowBias = 0.001; + Engine3D.setting.shadow.shadowBias = 0.0002; await Engine3D.init({}); diff --git a/samples/compute/Sample_FlowImg.ts b/samples/compute/Sample_FlowImg.ts index 14109f83..8e11dbb5 100644 --- a/samples/compute/Sample_FlowImg.ts +++ b/samples/compute/Sample_FlowImg.ts @@ -38,12 +38,12 @@ export class Demo_FlowImg { input.accept = '.png,.webp' input.style.position = 'fixed' document.body.appendChild(input) - /*input.onchange= async (e)=>{ + input.onchange= async (e)=>{ let url = URL.createObjectURL(e.target.files[0]) let image = await this.imageloader(url) simulator.setImageData(image); simulator.reset() - }*/ + } GUIHelp.addButton('Change Image (PNG with transparent)', ()=>{ input.click() }) diff --git a/samples/compute/cloth/ClothSimulator.ts b/samples/compute/cloth/ClothSimulator.ts index aaba5966..afed7823 100644 --- a/samples/compute/cloth/ClothSimulator.ts +++ b/samples/compute/cloth/ClothSimulator.ts @@ -60,8 +60,8 @@ export class ClothSimulator extends MeshRenderer { var mat = new LitMaterial(); mat.roughness = 0.8; mat.baseMap = Engine3D.res.redTexture; + mat.cullMode = 'none' this.material = mat; - this.material.doubleSide = true; } public start() { From 369a65a0ab8e99361d491e119f009c381345b525 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 24 Jul 2024 17:45:43 +0800 Subject: [PATCH 07/25] chore(docs): fix typedoc plugin script --- packages/graphic/package.json | 2 +- packages/media-extention/package.json | 2 +- packages/particle/package.json | 2 +- packages/stats/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/graphic/package.json b/packages/graphic/package.json index d6c5e73e..7b8020d1 100644 --- a/packages/graphic/package.json +++ b/packages/graphic/package.json @@ -14,7 +14,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/graphic/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/graphic index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.js --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/media-extention/package.json b/packages/media-extention/package.json index 5e90f42b..3ea64355 100644 --- a/packages/media-extention/package.json +++ b/packages/media-extention/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/media-extention/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/media-extention index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.js --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/particle/package.json b/packages/particle/package.json index ab16b5c7..52c34652 100644 --- a/packages/particle/package.json +++ b/packages/particle/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/particle dist/types && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/particle index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.js --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/stats/package.json b/packages/stats/package.json index 36ee8618..afb53f71 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/stats/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/stats index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.js --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { From b6b4bce92688b95c16212316854ce92c09d089a9 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Thu, 25 Jul 2024 14:16:48 +0800 Subject: [PATCH 08/25] chore: remove typedoc script --- package.json | 2 +- packages/graphic/package.json | 2 +- packages/media-extention/package.json | 2 +- packages/particle/package.json | 2 +- packages/physics/Physics.ts | 1 - packages/physics/package.json | 2 +- packages/post/index.ts | 2 +- packages/stats/package.json | 2 +- script/typedoc-plugin-not-exported.cjs | 73 -------------------------- 9 files changed, 7 insertions(+), 81 deletions(-) delete mode 100644 script/typedoc-plugin-not-exported.cjs diff --git a/package.json b/package.json index dd33a635..a1383277 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test": "electron test/ci/main.js", "test:ci": "xvfb-maybe -- electron --enable-unsafe-webgpu --enable-features=Vulkan --use-vulkan=swiftshader --use-webgpu-adapter=swiftshader --no-sandbox test/ci/main.js", "docs": "npm run docs:core && npm run docs:physics && npm run docs:media && npm run docs:stats && npm run docs:particle && npm run docs:graphic", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ./script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.build.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out", + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.build.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out", "docs:core": "npm run docs:typedoc docs/api src/index.ts", "docs:physics": "cd packages/physics && npm run docs", "docs:media": "cd packages/media-extention && npm run docs", diff --git a/packages/graphic/package.json b/packages/graphic/package.json index 7b8020d1..21f1eeba 100644 --- a/packages/graphic/package.json +++ b/packages/graphic/package.json @@ -14,7 +14,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/graphic/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/graphic index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/media-extention/package.json b/packages/media-extention/package.json index 3ea64355..6a2a7225 100644 --- a/packages/media-extention/package.json +++ b/packages/media-extention/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/media-extention/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/media-extention index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/particle/package.json b/packages/particle/package.json index 52c34652..e63c7790 100644 --- a/packages/particle/package.json +++ b/packages/particle/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/particle dist/types && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/particle index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/physics/Physics.ts b/packages/physics/Physics.ts index f1b938c3..95c714f7 100644 --- a/packages/physics/Physics.ts +++ b/packages/physics/Physics.ts @@ -5,7 +5,6 @@ import { Rigidbody } from './Rigidbody'; /** * Physics Engine * @group Plugin - * @notExported */ class _Physics { private _world: Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld; diff --git a/packages/physics/package.json b/packages/physics/package.json index c1ff8616..e9ff90bf 100644 --- a/packages/physics/package.json +++ b/packages/physics/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/physics/* dist && rm -rf dist/src & rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/physics index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/packages/post/index.ts b/packages/post/index.ts index 3f04d0c3..8d855261 100644 --- a/packages/post/index.ts +++ b/packages/post/index.ts @@ -1,6 +1,6 @@ /** * PostEffects Plugins * @group Plugin - * @notExported + * @internal */ export {} \ No newline at end of file diff --git a/packages/stats/package.json b/packages/stats/package.json index afb53f71..9147f621 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -13,7 +13,7 @@ "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", "build:clean": "mv dist/packages/stats/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/stats index.ts", - "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --plugin ../../script/typedoc-plugin-not-exported.cjs --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, "license": "MIT", "repository": { diff --git a/script/typedoc-plugin-not-exported.cjs b/script/typedoc-plugin-not-exported.cjs deleted file mode 100644 index bf03675e..00000000 --- a/script/typedoc-plugin-not-exported.cjs +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -/** - * typedoc-plugin-not-exported - * TypeDoc plugin that forces inclusion of non-exported symbols (variables) - */ -Object.defineProperty(exports, "__esModule", { value: true }); -const typedoc_1 = require("typedoc"); // version 0.20.16+ -const ModuleFlags = typedoc_1.TypeScript.SymbolFlags.ValueModule | typedoc_1.TypeScript.SymbolFlags.NamespaceModule; -exports.load = function (application) { - /** @type {Map>} */ - const checkedForModuleExports = new Map(); - let includeTag = 'notExported'; - application.options.addDeclaration({ - name: 'includeTag', - help: '[typedoc-plugin-not-exported] Specify the tag name for non-exported member to be imported under', - defaultValue: includeTag, - }); - application.converter.on(typedoc_1.Converter.EVENT_BEGIN, () => { - const includeTagTemp = application.options.getValue('includeTag'); - if (typeof includeTagTemp === 'string') { - includeTag = includeTagTemp.toLocaleLowerCase(); - } - }); - application.converter.on(typedoc_1.Converter.EVENT_CREATE_DECLARATION, lookForFakeExports); - application.converter.on(typedoc_1.Converter.EVENT_END, () => { - checkedForModuleExports.clear(); - }); - function lookForFakeExports(context, reflection) { - // Figure out where "not exports" will be placed, go up the tree until we get to - // the module where it belongs. - let targetModule = reflection; - while (!targetModule.kindOf(typedoc_1.ReflectionKind.Module | typedoc_1.ReflectionKind.Project)) { - targetModule = targetModule.parent; - } - const moduleContext = context.withScope(targetModule); - const reflSymbol = context.project.getSymbolFromReflection(reflection); - if (!reflSymbol) { - // Global file, no point in doing anything here. TypeDoc will already - // include everything declared in this file. - return; - } - for (const declaration of reflSymbol.declarations || []) { - checkFakeExportsOfFile(declaration.getSourceFile(), moduleContext); - } - } - function checkFakeExportsOfFile(file, context) { - const moduleSymbol = context.checker.getSymbolAtLocation(file); - // Make sure we are allowed to call getExportsOfModule - if (!moduleSymbol || (moduleSymbol.flags & ModuleFlags) === 0) { - return; - } - const checkedScopes = checkedForModuleExports.get(context.scope) || new Set(); - checkedForModuleExports.set(context.scope, checkedScopes); - if (checkedScopes.has(file)) - return; - checkedScopes.add(file); - const exportedSymbols = context.checker.getExportsOfModule(moduleSymbol); - const symbols = context.checker - .getSymbolsInScope(file, typedoc_1.TypeScript.SymbolFlags.ModuleMember) - .filter((symbol) => { - var _a; - return ((_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a.some((d) => d.getSourceFile() === file)) && - !exportedSymbols.includes(symbol); - }); - for (const symbol of symbols) { - if (symbol - .getJsDocTags() - .some((tag) => tag.name.toLocaleLowerCase() === includeTag)) { - context.converter.convertSymbol(context, symbol); - } - } - } -}; From 14c80e4814bd54ca5b02a05535c2d310a646ff87 Mon Sep 17 00:00:00 2001 From: Codeboy Date: Thu, 25 Jul 2024 23:48:32 +0800 Subject: [PATCH 09/25] chore: remove earcut npm package (#428) --- package.json | 1 - packages/graphic/index.ts | 3 +- packages/graphic/package.json | 3 +- packages/graphic/renderer/shape3d/Earcut.ts | 694 +++++++++++++++++ .../graphic/renderer/shape3d/LineShape3D.ts | 4 +- pnpm-lock.yaml | 695 +++++++++--------- public | 2 +- src/math/Vector2.ts | 12 + 8 files changed, 1070 insertions(+), 344 deletions(-) create mode 100644 packages/graphic/renderer/shape3d/Earcut.ts diff --git a/package.json b/package.json index a1383277..99a1ccea 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "devDependencies": { "@webgpu/types": "^0.1.40", "conventional-changelog-cli": "^2.2.2", - "earcut": "^2.2.4", "electron": "^31.1.0", "typedoc": "^0.25.7", "typedoc-plugin-markdown": "^3.17.1", diff --git a/packages/graphic/index.ts b/packages/graphic/index.ts index f5d81db2..3e97dbff 100644 --- a/packages/graphic/index.ts +++ b/packages/graphic/index.ts @@ -2,6 +2,7 @@ export * from "./renderer/GrassRenderer" export * from "./renderer/Shape3DRenderer" export * from "./renderer/Shape3DMaker" export * from "./renderer/shape3d/RoundRectShape3D" +export * from "./renderer/shape3d/Earcut" export * from "./renderer/shape3d/EllipseShape3D" export * from "./renderer/shape3d/CircleShape3D" export * from "./renderer/shape3d/QuadraticCurveShape3D" @@ -24,4 +25,4 @@ export * from "./renderer/graphic3d/Graphic3DFaceRenderer" export * from "./renderer/graphic3d/Graphic3DMesh" export * from "./renderer/graphic3d/Graphic3DMeshRenderer" export * from "./renderer/graphic3d/Graphic3DRibbonRenderer" -export * from "./renderer/graphic3d/ShapeInfo" \ No newline at end of file +export * from "./renderer/graphic3d/ShapeInfo" diff --git a/packages/graphic/package.json b/packages/graphic/package.json index 21f1eeba..9ed99e18 100644 --- a/packages/graphic/package.json +++ b/packages/graphic/package.json @@ -22,7 +22,6 @@ "url": "git+https://github.com/Orillusion/orillusion.git" }, "dependencies": { - "@orillusion/core": "^0.8.0", - "earcut": "^2.2.4" + "@orillusion/core": "^0.8.0" } } diff --git a/packages/graphic/renderer/shape3d/Earcut.ts b/packages/graphic/renderer/shape3d/Earcut.ts new file mode 100644 index 00000000..d2aad237 --- /dev/null +++ b/packages/graphic/renderer/shape3d/Earcut.ts @@ -0,0 +1,694 @@ +/** + * Port from https://github.com/mapbox/earcut (v2.2.4) + */ + +export class Earcut { + public static triangulate(data, holeIndices?, dim = 2) { + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; + + if (!outerNode || outerNode.next === outerNode.prev) + return triangles; + + let minX, minY, maxX, maxY, x, y, invSize; + + if (hasHoles) + outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + if (data.length > 80 * dim) { + + minX = maxX = data[0]; + minY = maxY = data[1]; + + for (let i = dim; i < outerLen; i += dim) { + + x = data[i]; + y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); + + return triangles; + } +} + +function middleInside(a, b) { + let p = a, + inside = false; + const px = (a.x + b.x) / 2, + py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; +} + +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case + +} + +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); +} + +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} + +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); +} + +function sign(num) { + return num > 0 ? 1 : num < 0 ? - 1 : 0; +} + +function intersects(p1, q1, p2, q2) { + + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); + + if (o1 !== o2 && o3 !== o4) return true; // general case + + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; + +} + +function getLeftmost(start) { + let p = start, leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + return leftmost; +} + +function linkedList(data, start, end, dim, clockwise) { + let i, last; + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); + } else { + for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); + } + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + return last; +} + +function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; + let i, len, start, end, list; + + for (i = 0, len = holeIndices.length; i < len; i++) { + + start = holeIndices[i] * dim; + end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + + } + + queue.sort(compareX); + + // process holes from left to right + for (i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; +} + +function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + const bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); +} + +function filterPoints(start, end?) { + + if (!start) return start; + if (!end) end = start; + + let p = start, + again; + do { + + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + + p = p.next; + + } + + } while (again || p !== end); + + return end; + +} + +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} + +function intersectsPolygon(a, b) { + let p = a; + do { + + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + + } while (p !== a); + + return false; +} + +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} + +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; +} + +function findHoleBridge(hole, outerNode) { + + let p = outerNode, + qx = - Infinity, + m; + + const hx = hole.x, hy = hole.y; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + do { + + if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + + } + + } + + p = p.next; + + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + const stop = m, + mx = m.x, + my = m.y; + let tanMin = Infinity, tan; + + p = m; + + do { + + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + + } + + p = p.next; + + } while (p !== stop); + + return m; + +} + +function compareX(a, b) { + return a.x - b.x; +} + +function sortLinked(list) { + + let i, p, q, e, tail, numMerges, pSize, qSize, + inSize = 1; + + do { + + p = list; + list = null; + tail = null; + numMerges = 0; + + while (p) { + + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + + pSize++; + q = q.nextZ; + if (!q) break; + + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + + e = p; + p = p.nextZ; + pSize--; + + } else { + + e = q; + q = q.nextZ; + qSize--; + + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + + } + + p = q; + + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; +} + +function zOrder(x, y, minX, minY, invSize) { + + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); +} + +function isEarHashed(ear, minX, minY, invSize) { + + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox; min & max are calculated like this for speed + const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx), + y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy), + x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx), + y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy); + + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + let p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + + } + + return true; + +} + +function cureLocalIntersections(start, triangles, dim) { + + let p = start; + do { + + const a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i / dim | 0); + triangles.push(p.i / dim | 0); + triangles.push(b.i / dim | 0); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + + } + + p = p.next; + + } while (p !== start); + + return filterPoints(p); + +} + +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + + let b = a.next.next; + while (b !== a.prev) { + + if (a.i !== b.i && isValidDiagonal(a, b)) { + + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + + } + + b = b.next; + + } + + a = a.next; + + } while (a !== start); + +} + +function isEar(ear) { + + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox; min & max are calculated like this for speed + const x0 = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx), + y0 = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy), + x1 = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx), + y1 = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy); + + let p = c.next; + while (p !== a) { + + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + + } + + return true; + +} + +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); + + let stop = ear, + prev, next; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + + prev = ear.prev; + next = ear.next; + + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + + // cut off the triangle + triangles.push(prev.i / dim | 0); + triangles.push(ear.i / dim | 0); + triangles.push(next.i / dim | 0); + + removeNode(ear); + + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + + // try filtering points and slicing again + if (!pass) { + + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); + + // if this didn't work, try curing all small self-intersections locally + + } else if (pass === 1) { + + ear = cureLocalIntersections(filterPoints(ear), triangles, dim); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); + + // as a last resort, try splitting the remaining polygon into two + + } else if (pass === 2) { + + splitEarcut(ear, triangles, dim, minX, minY, invSize); + + } + + break; + + } + + } + +} +function splitPolygon(a, b) { + + const a2 = new Node(a.i, a.x, a.y), + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + +} + +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + + const p = new Node(i, x, y); + + if (!last) { + + p.prev = p; + p.next = p; + + } else { + + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + + } + + return p; + +} + +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} + +function Node(i, x, y) { + // vertex index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertex nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = 0; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; +} + +function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} diff --git a/packages/graphic/renderer/shape3d/LineShape3D.ts b/packages/graphic/renderer/shape3d/LineShape3D.ts index 237b27a8..56866bd8 100644 --- a/packages/graphic/renderer/shape3d/LineShape3D.ts +++ b/packages/graphic/renderer/shape3d/LineShape3D.ts @@ -1,6 +1,6 @@ import { LineJoin } from "../graphic3d/Graphic3DFaceRenderer"; import { Point3D, Shape3D, ShapeTypeEnum } from "./Shape3D"; -import earcut from 'earcut'; +import { Earcut } from "./Earcut"; type vec3 = { x: number, y: number, h?: number }; @@ -45,7 +45,7 @@ export class LineShape3D extends Shape3D { for (let point of this._points3D) { coords.push(point.x, point.y); } - this._indecies = earcut(coords); + this._indecies = Earcut.triangulate(coords); // earcut(coords); this._srcIndexCount = this._indecies?.length || 0; } else { this._indecies = null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b72acf31..4aa5caf5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,42 +1,46 @@ -lockfileVersion: 5.4 - -specifiers: - '@webgpu/types': ^0.1.40 - conventional-changelog-cli: ^2.2.2 - earcut: ^2.2.4 - electron: ^31.1.0 - typedoc: ^0.25.7 - typedoc-plugin-markdown: ^3.17.1 - typescript: ^5.3.3 - vite: ^5.3.1 - xvfb-maybe: ^0.2.1 +lockfileVersion: '6.0' devDependencies: - '@webgpu/types': 0.1.40 - conventional-changelog-cli: 2.2.2 - earcut: 2.2.4 - electron: 31.1.0 - typedoc: 0.25.7_typescript@5.3.3 - typedoc-plugin-markdown: 3.17.1_typedoc@0.25.7 - typescript: 5.3.3 - vite: 5.3.3 - xvfb-maybe: 0.2.1 + '@webgpu/types': + specifier: ^0.1.40 + version: 0.1.40 + conventional-changelog-cli: + specifier: ^2.2.2 + version: 2.2.2 + electron: + specifier: ^31.1.0 + version: 31.1.0 + typedoc: + specifier: ^0.25.7 + version: 0.25.7(typescript@5.3.3) + typedoc-plugin-markdown: + specifier: ^3.17.1 + version: 3.17.1(typedoc@0.25.7) + typescript: + specifier: ^5.3.3 + version: 5.3.3 + vite: + specifier: ^5.3.1 + version: 5.3.3 + xvfb-maybe: + specifier: ^0.2.1 + version: 0.2.1 packages: - /@babel/code-frame/7.21.4: + /@babel/code-frame@7.21.4: resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 dev: true - /@babel/helper-validator-identifier/7.19.1: + /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} dev: true - /@babel/highlight/7.18.6: + /@babel/highlight@7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} dependencies: @@ -45,7 +49,7 @@ packages: js-tokens: 4.0.0 dev: true - /@electron/get/2.0.2: + /@electron/get@2.0.2: resolution: {integrity: sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==} engines: {node: '>=12'} dependencies: @@ -62,7 +66,7 @@ packages: - supports-color dev: true - /@esbuild/aix-ppc64/0.21.5: + /@esbuild/aix-ppc64@0.21.5: resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] @@ -71,25 +75,25 @@ packages: dev: true optional: true - /@esbuild/android-arm/0.21.5: - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} - cpu: [arm] + cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-arm64/0.21.5: - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} - cpu: [arm64] + cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@esbuild/android-x64/0.21.5: + /@esbuild/android-x64@0.21.5: resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] @@ -98,7 +102,7 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64/0.21.5: + /@esbuild/darwin-arm64@0.21.5: resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] @@ -107,7 +111,7 @@ packages: dev: true optional: true - /@esbuild/darwin-x64/0.21.5: + /@esbuild/darwin-x64@0.21.5: resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] @@ -116,7 +120,7 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64/0.21.5: + /@esbuild/freebsd-arm64@0.21.5: resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] @@ -125,7 +129,7 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64/0.21.5: + /@esbuild/freebsd-x64@0.21.5: resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] @@ -134,25 +138,25 @@ packages: dev: true optional: true - /@esbuild/linux-arm/0.21.5: - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} - cpu: [arm] + cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-arm64/0.21.5: - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} - cpu: [arm64] + cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@esbuild/linux-ia32/0.21.5: + /@esbuild/linux-ia32@0.21.5: resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] @@ -161,7 +165,7 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.21.5: + /@esbuild/linux-loong64@0.21.5: resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] @@ -170,7 +174,7 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el/0.21.5: + /@esbuild/linux-mips64el@0.21.5: resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] @@ -179,7 +183,7 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64/0.21.5: + /@esbuild/linux-ppc64@0.21.5: resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] @@ -188,7 +192,7 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64/0.21.5: + /@esbuild/linux-riscv64@0.21.5: resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] @@ -197,7 +201,7 @@ packages: dev: true optional: true - /@esbuild/linux-s390x/0.21.5: + /@esbuild/linux-s390x@0.21.5: resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] @@ -206,7 +210,7 @@ packages: dev: true optional: true - /@esbuild/linux-x64/0.21.5: + /@esbuild/linux-x64@0.21.5: resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] @@ -215,7 +219,7 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64/0.21.5: + /@esbuild/netbsd-x64@0.21.5: resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] @@ -224,7 +228,7 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64/0.21.5: + /@esbuild/openbsd-x64@0.21.5: resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] @@ -233,7 +237,7 @@ packages: dev: true optional: true - /@esbuild/sunos-x64/0.21.5: + /@esbuild/sunos-x64@0.21.5: resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] @@ -242,7 +246,7 @@ packages: dev: true optional: true - /@esbuild/win32-arm64/0.21.5: + /@esbuild/win32-arm64@0.21.5: resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] @@ -251,7 +255,7 @@ packages: dev: true optional: true - /@esbuild/win32-ia32/0.21.5: + /@esbuild/win32-ia32@0.21.5: resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] @@ -260,7 +264,7 @@ packages: dev: true optional: true - /@esbuild/win32-x64/0.21.5: + /@esbuild/win32-x64@0.21.5: resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] @@ -269,12 +273,12 @@ packages: dev: true optional: true - /@hutson/parse-repository-url/3.0.2: + /@hutson/parse-repository-url@3.0.2: resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} engines: {node: '>=6.9.0'} dev: true - /@rollup/rollup-android-arm-eabi/4.18.0: + /@rollup/rollup-android-arm-eabi@4.18.0: resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} cpu: [arm] os: [android] @@ -282,7 +286,7 @@ packages: dev: true optional: true - /@rollup/rollup-android-arm64/4.18.0: + /@rollup/rollup-android-arm64@4.18.0: resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} cpu: [arm64] os: [android] @@ -290,7 +294,7 @@ packages: dev: true optional: true - /@rollup/rollup-darwin-arm64/4.18.0: + /@rollup/rollup-darwin-arm64@4.18.0: resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} cpu: [arm64] os: [darwin] @@ -298,7 +302,7 @@ packages: dev: true optional: true - /@rollup/rollup-darwin-x64/4.18.0: + /@rollup/rollup-darwin-x64@4.18.0: resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} cpu: [x64] os: [darwin] @@ -306,7 +310,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf/4.18.0: + /@rollup/rollup-linux-arm-gnueabihf@4.18.0: resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} cpu: [arm] os: [linux] @@ -314,7 +318,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf/4.18.0: + /@rollup/rollup-linux-arm-musleabihf@4.18.0: resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} cpu: [arm] os: [linux] @@ -322,7 +326,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm64-gnu/4.18.0: + /@rollup/rollup-linux-arm64-gnu@4.18.0: resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} cpu: [arm64] os: [linux] @@ -330,7 +334,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm64-musl/4.18.0: + /@rollup/rollup-linux-arm64-musl@4.18.0: resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} cpu: [arm64] os: [linux] @@ -338,7 +342,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu/4.18.0: + /@rollup/rollup-linux-powerpc64le-gnu@4.18.0: resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} cpu: [ppc64] os: [linux] @@ -346,7 +350,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu/4.18.0: + /@rollup/rollup-linux-riscv64-gnu@4.18.0: resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} cpu: [riscv64] os: [linux] @@ -354,7 +358,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-s390x-gnu/4.18.0: + /@rollup/rollup-linux-s390x-gnu@4.18.0: resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} cpu: [s390x] os: [linux] @@ -362,7 +366,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-x64-gnu/4.18.0: + /@rollup/rollup-linux-x64-gnu@4.18.0: resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} cpu: [x64] os: [linux] @@ -370,7 +374,7 @@ packages: dev: true optional: true - /@rollup/rollup-linux-x64-musl/4.18.0: + /@rollup/rollup-linux-x64-musl@4.18.0: resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} cpu: [x64] os: [linux] @@ -378,7 +382,7 @@ packages: dev: true optional: true - /@rollup/rollup-win32-arm64-msvc/4.18.0: + /@rollup/rollup-win32-arm64-msvc@4.18.0: resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} cpu: [arm64] os: [win32] @@ -386,7 +390,7 @@ packages: dev: true optional: true - /@rollup/rollup-win32-ia32-msvc/4.18.0: + /@rollup/rollup-win32-ia32-msvc@4.18.0: resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} cpu: [ia32] os: [win32] @@ -394,7 +398,7 @@ packages: dev: true optional: true - /@rollup/rollup-win32-x64-msvc/4.18.0: + /@rollup/rollup-win32-x64-msvc@4.18.0: resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} cpu: [x64] os: [win32] @@ -402,19 +406,19 @@ packages: dev: true optional: true - /@sindresorhus/is/4.6.0: + /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} dev: true - /@szmarczak/http-timer/4.0.6: + /@szmarczak/http-timer@4.0.6: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} dependencies: defer-to-connect: 2.0.1 dev: true - /@types/cacheable-request/6.0.3: + /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: '@types/http-cache-semantics': 4.0.1 @@ -423,41 +427,41 @@ packages: '@types/responselike': 1.0.0 dev: true - /@types/estree/1.0.5: + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true - /@types/http-cache-semantics/4.0.1: + /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} dev: true - /@types/keyv/3.1.4: + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: '@types/node': 20.14.10 dev: true - /@types/minimist/1.2.2: + /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/20.14.10: + /@types/node@20.14.10: resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} dependencies: undici-types: 5.26.5 dev: true - /@types/normalize-package-data/2.4.1: + /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - /@types/responselike/1.0.0: + /@types/responselike@1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: '@types/node': 20.14.10 dev: true - /@types/yauzl/2.10.3: + /@types/yauzl@2.10.3: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: @@ -465,11 +469,11 @@ packages: dev: true optional: true - /@webgpu/types/0.1.40: + /@webgpu/types@0.1.40: resolution: {integrity: sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==} dev: true - /JSONStream/1.3.5: + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true dependencies: @@ -477,67 +481,68 @@ packages: through: 2.3.8 dev: true - /add-stream/1.0.0: + /add-stream@1.0.0: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} dev: true - /ansi-regex/5.0.1: + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} dev: true - /ansi-sequence-parser/1.1.0: + /ansi-sequence-parser@1.1.0: resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} dev: true - /ansi-styles/3.2.1: + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} dependencies: color-convert: 1.9.3 dev: true - /ansi-styles/4.3.0: + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: true - /array-ify/1.0.0: + /array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true - /arrify/1.0.1: + /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} dev: true - /balanced-match/1.0.2: + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true - /boolean/3.2.0: + /boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + requiresBuild: true dev: true optional: true - /brace-expansion/2.0.1: + /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 dev: true - /buffer-crc32/0.2.13: + /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true - /cacheable-lookup/5.0.4: + /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} dev: true - /cacheable-request/7.0.2: + /cacheable-request@7.0.2: resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} engines: {node: '>=8'} dependencies: @@ -550,7 +555,7 @@ packages: responselike: 2.0.1 dev: true - /camelcase-keys/6.2.2: + /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} engines: {node: '>=8'} dependencies: @@ -559,12 +564,12 @@ packages: quick-lru: 4.0.1 dev: true - /camelcase/5.3.1: + /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} dev: true - /chalk/2.4.2: + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} dependencies: @@ -573,7 +578,7 @@ packages: supports-color: 5.5.0 dev: true - /cliui/7.0.4: + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 @@ -581,41 +586,41 @@ packages: wrap-ansi: 7.0.0 dev: true - /clone-response/1.0.3: + /clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: mimic-response: 1.0.1 dev: true - /color-convert/1.9.3: + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 dev: true - /color-convert/2.0.1: + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: true - /color-name/1.1.3: + /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: true - /color-name/1.1.4: + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /compare-func/2.0.0: + /compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 dev: true - /conventional-changelog-angular/5.0.13: + /conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} dependencies: @@ -623,14 +628,14 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-atom/2.0.8: + /conventional-changelog-atom@2.0.8: resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-cli/2.2.2: + /conventional-changelog-cli@2.2.2: resolution: {integrity: sha512-8grMV5Jo8S0kP3yoMeJxV2P5R6VJOqK72IiSV9t/4H5r/HiRqEBQ83bYGuz4Yzfdj4bjaAEhZN/FFbsFXr5bOA==} engines: {node: '>=10'} hasBin: true @@ -643,14 +648,14 @@ packages: tempfile: 3.0.0 dev: true - /conventional-changelog-codemirror/2.0.8: + /conventional-changelog-codemirror@2.0.8: resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-conventionalcommits/4.6.3: + /conventional-changelog-conventionalcommits@4.6.3: resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} engines: {node: '>=10'} dependencies: @@ -659,7 +664,7 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-core/4.2.4: + /conventional-changelog-core@4.2.4: resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} engines: {node: '>=10'} dependencies: @@ -679,35 +684,35 @@ packages: through2: 4.0.2 dev: true - /conventional-changelog-ember/2.0.9: + /conventional-changelog-ember@2.0.9: resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-eslint/3.0.9: + /conventional-changelog-eslint@3.0.9: resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-express/2.0.6: + /conventional-changelog-express@2.0.6: resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-jquery/3.0.11: + /conventional-changelog-jquery@3.0.11: resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-jshint/2.0.9: + /conventional-changelog-jshint@2.0.9: resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} engines: {node: '>=10'} dependencies: @@ -715,12 +720,12 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-preset-loader/2.3.4: + /conventional-changelog-preset-loader@2.3.4: resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} engines: {node: '>=10'} dev: true - /conventional-changelog-writer/5.0.1: + /conventional-changelog-writer@5.0.1: resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} engines: {node: '>=10'} hasBin: true @@ -736,7 +741,7 @@ packages: through2: 4.0.2 dev: true - /conventional-changelog/3.1.25: + /conventional-changelog@3.1.25: resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} engines: {node: '>=10'} dependencies: @@ -753,7 +758,7 @@ packages: conventional-changelog-preset-loader: 2.3.4 dev: true - /conventional-commits-filter/2.0.7: + /conventional-commits-filter@2.0.7: resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} engines: {node: '>=10'} dependencies: @@ -761,7 +766,7 @@ packages: modify-values: 1.0.1 dev: true - /conventional-commits-parser/3.2.4: + /conventional-commits-parser@3.2.4: resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} engines: {node: '>=10'} hasBin: true @@ -774,20 +779,20 @@ packages: through2: 4.0.2 dev: true - /core-util-is/1.0.3: + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /dargs/7.0.0: + /dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} dev: true - /dateformat/3.0.3: + /dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} dev: true - /debug/2.6.9: + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: supports-color: '*' @@ -798,7 +803,7 @@ packages: ms: 2.0.0 dev: true - /debug/4.3.4: + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -810,7 +815,7 @@ packages: ms: 2.1.2 dev: true - /decamelize-keys/1.1.1: + /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} dependencies: @@ -818,49 +823,47 @@ packages: map-obj: 1.0.1 dev: true - /decamelize/1.2.0: + /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} dev: true - /decompress-response/6.0.0: + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} dependencies: mimic-response: 3.1.0 dev: true - /defer-to-connect/2.0.1: + /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} dev: true - /define-properties/1.2.0: + /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} + requiresBuild: true dependencies: has-property-descriptors: 1.0.0 object-keys: 1.1.1 dev: true optional: true - /detect-node/2.1.0: + /detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + requiresBuild: true dev: true optional: true - /dot-prop/5.3.0: + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} dependencies: is-obj: 2.0.0 dev: true - /earcut/2.2.4: - resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} - dev: true - - /electron/31.1.0: + /electron@31.1.0: resolution: {integrity: sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==} engines: {node: '>= 12.20.55'} hasBin: true @@ -873,33 +876,34 @@ packages: - supports-color dev: true - /emoji-regex/8.0.0: + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true - /end-of-stream/1.4.4: + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 dev: true - /env-paths/2.2.1: + /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} dev: true - /error-ex/1.3.2: + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 dev: true - /es6-error/4.1.1: + /es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + requiresBuild: true dev: true optional: true - /esbuild/0.21.5: + /esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true @@ -930,23 +934,24 @@ packages: '@esbuild/win32-x64': 0.21.5 dev: true - /escalade/3.1.1: + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} dev: true - /escape-string-regexp/1.0.5: + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} dev: true - /escape-string-regexp/4.0.0: + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + requiresBuild: true dev: true optional: true - /extract-zip/2.0.1: + /extract-zip@2.0.1: resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} engines: {node: '>= 10.17.0'} hasBin: true @@ -960,20 +965,20 @@ packages: - supports-color dev: true - /fd-slicer/1.1.0: + /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: pend: 1.2.0 dev: true - /find-up/2.1.0: + /find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} dependencies: locate-path: 2.0.0 dev: true - /find-up/4.1.0: + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} dependencies: @@ -981,7 +986,7 @@ packages: path-exists: 4.0.0 dev: true - /fs-extra/8.1.0: + /fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} dependencies: @@ -990,7 +995,7 @@ packages: universalify: 0.1.2 dev: true - /fsevents/2.3.3: + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -998,17 +1003,18 @@ packages: dev: true optional: true - /function-bind/1.1.1: + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /get-caller-file/2.0.5: + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-intrinsic/1.2.1: + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + requiresBuild: true dependencies: function-bind: 1.1.1 has: 1.0.3 @@ -1017,7 +1023,7 @@ packages: dev: true optional: true - /get-pkg-repo/4.2.1: + /get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} hasBin: true @@ -1028,14 +1034,14 @@ packages: yargs: 16.2.0 dev: true - /get-stream/5.2.0: + /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} dependencies: pump: 3.0.0 dev: true - /git-raw-commits/2.0.11: + /git-raw-commits@2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} hasBin: true @@ -1047,7 +1053,7 @@ packages: through2: 4.0.2 dev: true - /git-remote-origin-url/2.0.0: + /git-remote-origin-url@2.0.0: resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} engines: {node: '>=4'} dependencies: @@ -1055,7 +1061,7 @@ packages: pify: 2.3.0 dev: true - /git-semver-tags/4.1.1: + /git-semver-tags@4.1.1: resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} engines: {node: '>=10'} hasBin: true @@ -1064,13 +1070,13 @@ packages: semver: 6.3.0 dev: true - /gitconfiglocal/1.0.0: + /gitconfiglocal@1.0.0: resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} dependencies: ini: 1.3.8 dev: true - /global-agent/3.0.0: + /global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} requiresBuild: true @@ -1084,15 +1090,16 @@ packages: dev: true optional: true - /globalthis/1.0.3: + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} + requiresBuild: true dependencies: define-properties: 1.2.0 dev: true optional: true - /got/11.8.6: + /got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} dependencies: @@ -1109,11 +1116,11 @@ packages: responselike: 2.0.1 dev: true - /graceful-fs/4.2.11: + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true - /handlebars/4.7.7: + /handlebars@4.7.7: resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} engines: {node: '>=0.4.7'} hasBin: true @@ -1126,58 +1133,61 @@ packages: uglify-js: 3.17.4 dev: true - /hard-rejection/2.1.0: + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} dev: true - /has-flag/3.0.0: + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} dev: true - /has-property-descriptors/1.0.0: + /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + requiresBuild: true dependencies: get-intrinsic: 1.2.1 dev: true optional: true - /has-proto/1.0.1: + /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + requiresBuild: true dev: true optional: true - /has-symbols/1.0.3: + /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + requiresBuild: true dev: true optional: true - /has/1.0.3: + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 dev: true - /hosted-git-info/2.8.9: + /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /hosted-git-info/4.1.0: + /hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} dependencies: lru-cache: 6.0.0 dev: true - /http-cache-semantics/4.1.1: + /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: true - /http2-wrapper/1.0.3: + /http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} dependencies: @@ -1185,110 +1195,110 @@ packages: resolve-alpn: 1.2.1 dev: true - /indent-string/4.0.0: + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} dev: true - /inherits/2.0.4: + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /ini/1.3.8: + /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /is-arrayish/0.2.1: + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true - /is-core-module/2.12.1: + /is-core-module@2.12.1: resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} dependencies: has: 1.0.3 dev: true - /is-fullwidth-code-point/3.0.0: + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} dev: true - /is-obj/2.0.0: + /is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} dev: true - /is-plain-obj/1.1.0: + /is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} dev: true - /is-text-path/1.0.1: + /is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} dependencies: text-extensions: 1.9.0 dev: true - /isarray/1.0.0: + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true - /isexe/2.0.0: + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /js-tokens/4.0.0: + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /json-buffer/3.0.1: + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true - /json-parse-better-errors/1.0.2: + /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: true - /json-parse-even-better-errors/2.3.1: + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - /json-stringify-safe/5.0.1: + /json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: true - /jsonc-parser/3.2.0: + /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true - /jsonfile/4.0.0: + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: graceful-fs: 4.2.11 dev: true - /jsonparse/1.3.1: + /jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} dev: true - /keyv/4.5.2: + /keyv@4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: json-buffer: 3.0.1 dev: true - /kind-of/6.0.3: + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} dev: true - /lines-and-columns/1.2.4: + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /load-json-file/4.0.0: + /load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} dependencies: @@ -1298,7 +1308,7 @@ packages: strip-bom: 3.0.0 dev: true - /locate-path/2.0.0: + /locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} dependencies: @@ -1306,62 +1316,63 @@ packages: path-exists: 3.0.0 dev: true - /locate-path/5.0.0: + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} dependencies: p-locate: 4.1.0 dev: true - /lodash.ismatch/4.4.0: + /lodash.ismatch@4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} dev: true - /lodash/4.17.21: + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true - /lowercase-keys/2.0.0: + /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} dev: true - /lru-cache/6.0.0: + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} dependencies: yallist: 4.0.0 dev: true - /lunr/2.3.9: + /lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} dev: true - /map-obj/1.0.1: + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} dev: true - /map-obj/4.3.0: + /map-obj@4.3.0: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} dev: true - /marked/4.3.0: + /marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} hasBin: true dev: true - /matcher/3.0.0: + /matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} + requiresBuild: true dependencies: escape-string-regexp: 4.0.0 dev: true optional: true - /meow/8.1.2: + /meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} dependencies: @@ -1378,29 +1389,29 @@ packages: yargs-parser: 20.2.9 dev: true - /mimic-response/1.0.1: + /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} dev: true - /mimic-response/3.1.0: + /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} dev: true - /min-indent/1.0.1: + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} dev: true - /minimatch/9.0.3: + /minimatch@9.0.3: resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 dev: true - /minimist-options/4.1.0: + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} dependencies: @@ -1409,34 +1420,34 @@ packages: kind-of: 6.0.3 dev: true - /minimist/1.2.8: + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true - /modify-values/1.0.1: + /modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} dev: true - /ms/2.0.0: + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true - /ms/2.1.2: + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - /nanoid/3.3.7: + /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: true - /neo-async/2.6.2: + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /normalize-package-data/2.5.0: + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 @@ -1445,7 +1456,7 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-package-data/3.0.3: + /normalize-package-data@3.0.3: resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} dependencies: @@ -1455,67 +1466,68 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-url/6.1.0: + /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} dev: true - /object-keys/1.1.1: + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + requiresBuild: true dev: true optional: true - /once/1.4.0: + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true - /p-cancelable/2.1.1: + /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} dev: true - /p-limit/1.3.0: + /p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} dependencies: p-try: 1.0.0 dev: true - /p-limit/2.3.0: + /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} dependencies: p-try: 2.2.0 dev: true - /p-locate/2.0.0: + /p-locate@2.0.0: resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} engines: {node: '>=4'} dependencies: p-limit: 1.3.0 dev: true - /p-locate/4.1.0: + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} dependencies: p-limit: 2.3.0 dev: true - /p-try/1.0.0: + /p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} dev: true - /p-try/2.2.0: + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} dev: true - /parse-json/4.0.0: + /parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} dependencies: @@ -1523,7 +1535,7 @@ packages: json-parse-better-errors: 1.0.2 dev: true - /parse-json/5.2.0: + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: @@ -1533,46 +1545,46 @@ packages: lines-and-columns: 1.2.4 dev: true - /path-exists/3.0.0: + /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} dev: true - /path-exists/4.0.0: + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} dev: true - /path-parse/1.0.7: + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-type/3.0.0: + /path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} dependencies: pify: 3.0.0 dev: true - /pend/1.2.0: + /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true - /picocolors/1.0.1: + /picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} dev: true - /pify/2.3.0: + /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} dev: true - /pify/3.0.0: + /pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} dev: true - /postcss/8.4.39: + /postcss@8.4.39: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} dependencies: @@ -1581,38 +1593,38 @@ packages: source-map-js: 1.2.0 dev: true - /process-nextick-args/2.0.1: + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true - /progress/2.0.3: + /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} dev: true - /pump/3.0.0: + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 dev: true - /q/1.5.1: + /q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true - /quick-lru/4.0.1: + /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} dev: true - /quick-lru/5.1.1: + /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} dev: true - /read-pkg-up/3.0.0: + /read-pkg-up@3.0.0: resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} engines: {node: '>=4'} dependencies: @@ -1620,7 +1632,7 @@ packages: read-pkg: 3.0.0 dev: true - /read-pkg-up/7.0.1: + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} dependencies: @@ -1629,7 +1641,7 @@ packages: type-fest: 0.8.1 dev: true - /read-pkg/3.0.0: + /read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} dependencies: @@ -1638,7 +1650,7 @@ packages: path-type: 3.0.0 dev: true - /read-pkg/5.2.0: + /read-pkg@5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} dependencies: @@ -1648,7 +1660,7 @@ packages: type-fest: 0.6.0 dev: true - /readable-stream/2.3.8: + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 @@ -1660,7 +1672,7 @@ packages: util-deprecate: 1.0.2 dev: true - /readable-stream/3.6.2: + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} dependencies: @@ -1669,7 +1681,7 @@ packages: util-deprecate: 1.0.2 dev: true - /redent/3.0.0: + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} dependencies: @@ -1677,16 +1689,16 @@ packages: strip-indent: 3.0.0 dev: true - /require-directory/2.1.1: + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: true - /resolve-alpn/1.2.1: + /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} dev: true - /resolve/1.22.2: + /resolve@1.22.2: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true dependencies: @@ -1695,15 +1707,16 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /responselike/2.0.1: + /responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} dependencies: lowercase-keys: 2.0.0 dev: true - /roarr/2.15.4: + /roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} + requiresBuild: true dependencies: boolean: 3.2.0 detect-node: 2.1.0 @@ -1714,7 +1727,7 @@ packages: dev: true optional: true - /rollup/4.18.0: + /rollup@4.18.0: resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1740,30 +1753,31 @@ packages: fsevents: 2.3.3 dev: true - /safe-buffer/5.1.2: + /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true - /safe-buffer/5.2.1: + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /semver-compare/1.0.0: + /semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + requiresBuild: true dev: true optional: true - /semver/5.7.1: + /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true dev: true - /semver/6.3.0: + /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true dev: true - /semver/7.5.1: + /semver@7.5.1: resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} engines: {node: '>=10'} hasBin: true @@ -1771,15 +1785,16 @@ packages: lru-cache: 6.0.0 dev: true - /serialize-error/7.0.1: + /serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} + requiresBuild: true dependencies: type-fest: 0.13.1 dev: true optional: true - /shiki/0.14.7: + /shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} dependencies: ansi-sequence-parser: 1.1.0 @@ -1788,56 +1803,57 @@ packages: vscode-textmate: 8.0.0 dev: true - /source-map-js/1.2.0: + /source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true - /source-map/0.6.1: + /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} dev: true - /spdx-correct/3.2.0: + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.13 dev: true - /spdx-exceptions/2.3.0: + /spdx-exceptions@2.3.0: resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} dev: true - /spdx-expression-parse/3.0.1: + /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.3.0 spdx-license-ids: 3.0.13 dev: true - /spdx-license-ids/3.0.13: + /spdx-license-ids@3.0.13: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true - /split/1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + /split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: - through: 2.3.8 + readable-stream: 3.6.2 dev: true - /split2/3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + /split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} dependencies: - readable-stream: 3.6.2 + through: 2.3.8 dev: true - /sprintf-js/1.1.2: + /sprintf-js@1.1.2: resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + requiresBuild: true dev: true optional: true - /string-width/4.2.3: + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} dependencies: @@ -1846,38 +1862,38 @@ packages: strip-ansi: 6.0.1 dev: true - /string_decoder/1.1.1: + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 dev: true - /string_decoder/1.3.0: + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 dev: true - /strip-ansi/6.0.1: + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 dev: true - /strip-bom/3.0.0: + /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} dev: true - /strip-indent/3.0.0: + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} dependencies: min-indent: 1.0.1 dev: true - /sumchecker/3.0.1: + /sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} dependencies: @@ -1886,24 +1902,24 @@ packages: - supports-color dev: true - /supports-color/5.5.0: + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} dependencies: has-flag: 3.0.0 dev: true - /supports-preserve-symlinks-flag/1.0.0: + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: true - /temp-dir/2.0.0: + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} dev: true - /tempfile/3.0.0: + /tempfile@3.0.0: resolution: {integrity: sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw==} engines: {node: '>=8'} dependencies: @@ -1911,64 +1927,65 @@ packages: uuid: 3.4.0 dev: true - /text-extensions/1.9.0: + /text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} dev: true - /through/2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true - - /through2/2.0.5: + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: readable-stream: 2.3.8 xtend: 4.0.2 dev: true - /through2/4.0.2: + /through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: readable-stream: 3.6.2 dev: true - /trim-newlines/3.0.1: + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} dev: true - /type-fest/0.13.1: + /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} + requiresBuild: true dev: true optional: true - /type-fest/0.18.1: + /type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} dev: true - /type-fest/0.6.0: + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} dev: true - /type-fest/0.8.1: + /type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} dev: true - /typedoc-plugin-markdown/3.17.1_typedoc@0.25.7: + /typedoc-plugin-markdown@3.17.1(typedoc@0.25.7): resolution: {integrity: sha512-QzdU3fj0Kzw2XSdoL15ExLASt2WPqD7FbLeaqwT70+XjKyTshBnUlQA5nNREO1C2P8Uen0CDjsBLMsCQ+zd0lw==} peerDependencies: typedoc: '>=0.24.0' dependencies: handlebars: 4.7.7 - typedoc: 0.25.7_typescript@5.3.3 + typedoc: 0.25.7(typescript@5.3.3) dev: true - /typedoc/0.25.7_typescript@5.3.3: + /typedoc@0.25.7(typescript@5.3.3): resolution: {integrity: sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==} engines: {node: '>= 16'} hasBin: true @@ -1982,13 +1999,13 @@ packages: typescript: 5.3.3 dev: true - /typescript/5.3.3: + /typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true dev: true - /uglify-js/3.17.4: + /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} hasBin: true @@ -1996,33 +2013,33 @@ packages: dev: true optional: true - /undici-types/5.26.5: + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: true - /universalify/0.1.2: + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} dev: true - /util-deprecate/1.0.2: + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /uuid/3.4.0: + /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true dev: true - /validate-npm-package-license/3.0.4: + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 dev: true - /vite/5.3.3: + /vite@5.3.3: resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -2057,26 +2074,26 @@ packages: fsevents: 2.3.3 dev: true - /vscode-oniguruma/1.7.0: + /vscode-oniguruma@1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: true - /vscode-textmate/8.0.0: + /vscode-textmate@8.0.0: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: true - /which/1.3.1: + /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true dependencies: isexe: 2.0.0 dev: true - /wordwrap/1.0.0: + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: true - /wrap-ansi/7.0.0: + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} dependencies: @@ -2085,16 +2102,16 @@ packages: strip-ansi: 6.0.1 dev: true - /wrappy/1.0.2: + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /xtend/4.0.2: + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} dev: true - /xvfb-maybe/0.2.1: + /xvfb-maybe@0.2.1: resolution: {integrity: sha512-9IyRz3l6Qyhl6LvnGRF5jMPB4oBEepQnuzvVAFTynP6ACLLSevqigICJ9d/+ofl29m2daeaVBChnPYUnaeJ7yA==} hasBin: true requiresBuild: true @@ -2105,21 +2122,21 @@ packages: - supports-color dev: true - /y18n/5.0.8: + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} dev: true - /yallist/4.0.0: + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yargs-parser/20.2.9: + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} dev: true - /yargs/16.2.0: + /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} dependencies: @@ -2132,9 +2149,13 @@ packages: yargs-parser: 20.2.9 dev: true - /yauzl/2.10.0: + /yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 dev: true + +settings: + autoInstallPeers: false + excludeLinksFromLockfile: false diff --git a/public b/public index 5a6f7231..eeeaca67 160000 --- a/public +++ b/public @@ -1 +1 @@ -Subproject commit 5a6f72318f7eac3cacde980adc3a3a108c2b0035 +Subproject commit eeeaca67f9ada0a09cc9d6fb93d3fe449bf4ff69 diff --git a/src/math/Vector2.ts b/src/math/Vector2.ts index b082781b..b66c5537 100644 --- a/src/math/Vector2.ts +++ b/src/math/Vector2.ts @@ -276,6 +276,18 @@ export class Vector2 { return this; } + /** + * Add scaling vector + * @param v Source vector + * @param size Scale size + * @returns + */ + public addScaledVector(v: Vector2, size: number): Vector2 { + this.x += v.x * size; + this.y += v.y * size; + return this; + } + /** * Take the dot product of two vectors. * @param value Target vector From 1922f185f67b450dcbb04216ee30dfba8cc0e0a2 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Sat, 27 Jul 2024 16:54:12 +0800 Subject: [PATCH 10/25] fix(transform): fix lookAt at vertical angle (#431) --- src/math/Matrix4.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/math/Matrix4.ts b/src/math/Matrix4.ts index 559d1ad9..97c6fbf8 100644 --- a/src/math/Matrix4.ts +++ b/src/math/Matrix4.ts @@ -362,7 +362,7 @@ export class Matrix4 { if (Math.abs(up.z) > 0.9999) { zAxis.x += 0.0001; } else { - zAxis.z += 0.0001; + zAxis.z -= 0.0001; } zAxis.normalize(); } From cc90b82d4d9ab8250553263e3c0499a84e3e503c Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Sat, 27 Jul 2024 17:42:01 +0800 Subject: [PATCH 11/25] fix(inputsystem): capture pointer on pointerdown (#432) remove pointerleave pointerout --- src/components/controller/OrbitController.ts | 10 ++++----- src/io/InputSystem.ts | 23 +++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/components/controller/OrbitController.ts b/src/components/controller/OrbitController.ts index 0be8b249..2dd64c0c 100644 --- a/src/components/controller/OrbitController.ts +++ b/src/components/controller/OrbitController.ts @@ -265,10 +265,10 @@ export class OrbitController extends ComponentBase { this._isPanning = false; } } - private onPointerLeave() { - this._isMouseDown = false; - this._isPanning = false; - } + // private onPointerLeave() { + // this._isMouseDown = false; + // this._isPanning = false; + // } /** * @internal */ @@ -285,7 +285,6 @@ export class OrbitController extends ComponentBase { Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_DOWN, this.onPointerDown, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_MOVE, this.onPointerMove, this); Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_UP, this.onPointerUp, this); - Engine3D.inputSystem.addEventListener(PointerEvent3D.POINTER_OUT, this.onPointerLeave, this); } /** * @internal @@ -295,7 +294,6 @@ export class OrbitController extends ComponentBase { Engine3D.inputSystem.removeEventListener(PointerEvent3D.POINTER_DOWN, this.onPointerDown, this); Engine3D.inputSystem.removeEventListener(PointerEvent3D.POINTER_MOVE, this.onPointerMove, this); Engine3D.inputSystem.removeEventListener(PointerEvent3D.POINTER_UP, this.onPointerUp, this); - Engine3D.inputSystem.removeEventListener(PointerEvent3D.POINTER_OUT, this.onPointerLeave, this); } } diff --git a/src/io/InputSystem.ts b/src/io/InputSystem.ts index 64b9d1cf..1316e8de 100644 --- a/src/io/InputSystem.ts +++ b/src/io/InputSystem.ts @@ -87,8 +87,6 @@ export class InputSystem extends CEventDispatcher { protected _windowsEvent3d: CEvent; mouseLock: boolean = false; - - /** * init the input system * @param canvas the reference of canvas @@ -111,6 +109,7 @@ export class InputSystem extends CEventDispatcher { this.isRightMouseDown = true this.mouseStart(ev); } + canvas.setPointerCapture(ev.pointerId) } canvas.onpointerup = (ev: PointerEvent) => { if (ev.button == 0) { @@ -124,6 +123,7 @@ export class InputSystem extends CEventDispatcher { if(ev.button === _button && performance.now() - _t < 300 && Math.abs(_x - ev.clientX) < 20 && Math.abs(_y - ev.clientY) < 20){ ev.button === 0 ? this.mouseClick(ev) : this.rightClick(ev); } + canvas.releasePointerCapture(ev.pointerId) } canvas.onpointerenter = (ev: PointerEvent) => { this.mouseOver(ev); @@ -132,14 +132,18 @@ export class InputSystem extends CEventDispatcher { this.mouseMove(ev); } canvas.onpointercancel = (ev: PointerEvent) => { - this.mouseEnd(ev); - } - canvas.onpointerleave = (ev: PointerEvent) => { - this.mouseEnd(ev); - } - canvas.onpointerout = (ev: PointerEvent) => { - this.mouseEnd(ev); + canvas.releasePointerCapture(ev.pointerId) + if (ev.button == 1) + this.middleUp(ev); + else + this.mouseEnd(ev); } + // canvas.onpointerleave = (ev: PointerEvent) => { + // this.mouseEnd(ev); + // } + // canvas.onpointerout = (ev: PointerEvent) => { + // this.mouseEnd(ev); + // } // let input = document.createElement(`input`); // input.setSelectionRange(-1000, 1000); @@ -332,7 +336,6 @@ export class InputSystem extends CEventDispatcher { this.dispatchEvent(this._pointerEvent3D); } - private mouseStart(e: PointerEvent | MouseEvent) { this.isMouseDown = true; From fc74fa1a369a48de72bc888fd76b46d6b57a9089 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Sun, 28 Jul 2024 04:29:42 +0800 Subject: [PATCH 12/25] chore: clear shader docs --- packages/debug/GUIHelp.ts | 1 + .../effect/grass/shader/GrassBaseShader.ts | 3 + .../grass/shader/GrassCastShadowShader.ts | 3 + packages/effect/grass/shader/GrassShader.ts | 3 + .../shader/GrassVertexAttributeShader.ts | 3 + .../compute/graphic3d/Graphic3DShader.ts | 3 + .../compute/graphic3d/GraphicDecCompute.ts | 3 + .../graphic3d/GraphicDynamicCompute.ts | 3 + .../compute/graphic3d/GraphicFaceComput2.ts | 3 + .../compute/graphic3d/GraphicFaceCompute.ts | 3 + .../compute/graphic3d/GraphicFaceCompute3.ts | 3 + .../compute/graphic3d/GraphicLineCompute.ts | 3 + .../compute/graphic3d/GraphicTrailCompute.ts | 3 + .../compute/graphic3d/GraphicTrailCompute2.ts | 3 + .../compute/grass/GrassAnimCompute_cs.ts | 3 + .../compute/grass/GrassGeometryCompute_cs.ts | 3 + .../compute/shape3d/CircleShape3DCode_cs.ts | 3 + .../compute/shape3d/EllipseShape3DCode_cs.ts | 3 + .../compute/shape3d/Path2DShape3DCode_cs.ts | 3 + .../compute/shape3d/Path3DShape3DCode_cs.ts | 3 + .../shape3d/RoundRectShape3DCode_cs.ts | 3 + .../compute/shape3d/Shape3DCommonCode_cs.ts | 3 + .../shape3d/Shape3DKeyPointCompute_cs.ts | 3 + .../shape3d/Shape3DVertexCompute_cs.ts | 3 + .../shape3d/Shape3DVertexFillZero_cs.ts | 3 + packages/media-extention/ChromaKeyShader.ts | 3 + packages/media-extention/VideoShader.ts | 3 + .../particle/shader/ParticleRenderShader.ts | 3 + .../shader/Particle_Mass_UnLight_shader.ts | 1 + .../compute_density_pressure_compute.ts | 1 + packages/physics/Physics.ts | 3 + .../shader/anim/SkeletonAnimation_shader.ts | 3 + .../shader/cluster/ClusterBoundsSource_cs.ts | 3 + .../shader/cluster/ClusterLighting_cs.ts | 3 + src/assets/shader/compute/BLUR_CsShader.ts | 3 + src/assets/shader/compute/BRDFLUT.ts | 4 +- src/assets/shader/compute/BloomEffect_cs.ts | 29 +- .../shader/compute/BlurEffectCreator_cs.ts | 6 + .../shader/compute/DDGIIrradiance_Cs.ts | 3 + .../shader/compute/DDGILighting_CSShader.ts | 3 + src/assets/shader/compute/DepthOfView_cs.ts | 3 + .../compute/ErpImage2CubeMapCreateCube_cs.ts | 3 + .../compute/ErpImage2CubeMapRgbe2rgba_cs.ts | 3 + src/assets/shader/compute/GTAO_cs.ts | 3 + src/assets/shader/compute/GodRay_cs.ts | 3 + .../shader/compute/IBLEnvMapCreator_cs.ts | 3 + src/assets/shader/compute/MergeRGBA_cs.ts | 3 + .../shader/compute/MultiBouncePass_cs.ts | 4 +- .../shader/compute/OutLineBlendColor_cs.ts | 3 + .../shader/compute/OutlineCalcOutline_cs.ts | 4 +- src/assets/shader/compute/Outline_cs.ts | 3 + src/assets/shader/compute/Picker_cs.ts | 4 +- .../compute/PreFilteredEnvironment_cs copy.ts | 3 + .../compute/PreFilteredEnvironment_cs.ts | 3 + src/assets/shader/compute/PreIntegratedLut.ts | 4 +- src/assets/shader/compute/SSAO_cs.ts | 3 + src/assets/shader/compute/SSGI2_cs.ts | 3 + .../shader/compute/SSR_BlendColor_cs.ts | 3 + src/assets/shader/compute/SSR_IS_cs.ts | 3 + src/assets/shader/compute/SSR_RayTrace_cs.ts | 3 + src/assets/shader/compute/TAACopyTex_cs.ts | 3 + src/assets/shader/compute/TAASharpTex_cs.ts | 3 + src/assets/shader/compute/TAA_cs.ts | 3 + src/assets/shader/compute/utils/Combine_cs.ts | 3 + .../shader/compute/utils/Denoising_cs.ts | 3 + .../compute/utils/TestComputeLoadBuffer.ts | 3 + src/assets/shader/compute/utils/tw.ts | 3 + src/assets/shader/core/base/Common_frag.ts | 3 + src/assets/shader/core/base/Common_vert.ts | 3 + src/assets/shader/core/common/BrdfLut_frag.ts | 3 + src/assets/shader/core/common/EnvMap_frag.ts | 3 + src/assets/shader/core/common/GBufferStand.ts | 3 + .../shader/core/common/GlobalUniform.ts | 3 + .../shader/core/common/InstanceUniform.ts | 3 + .../shader/core/common/SHCommon_frag.ts | 4 +- .../shader/core/common/WorldMatrixUniform.ts | 3 + src/assets/shader/core/inline/Inline_vert.ts | 3 + .../shader/core/pass/CastShadow_pass.ts | 13 + .../shader/core/pass/FrustumCulling_cs.ts | 3 + src/assets/shader/core/pass/GBuffer_pass.ts | 3 + .../shader/core/pass/SkyGBuffer_pass.ts | 3 + src/assets/shader/core/pass/ZPassShader_cs.ts | 3 + src/assets/shader/core/pass/ZPassShader_fs.ts | 3 + src/assets/shader/core/pass/ZPassShader_vs.ts | 3 + src/assets/shader/core/struct/ClusterLight.ts | 3 + .../core/struct/ColorPassFragmentOutput.ts | 5 +- .../shader/core/struct/FragmentOutput.ts | 5 +- .../shader/core/struct/FragmentVarying.ts | 4 +- src/assets/shader/core/struct/ShadingInput.ts | 3 + .../core/struct/VertexAttributeIndexShader.ts | 4 +- .../shader/core/struct/VertexAttributes.ts | 3 + src/assets/shader/glsl/Quad_glsl.ts | 5 +- src/assets/shader/lighting/BRDF_frag.ts | 3 + src/assets/shader/lighting/BsDF_frag.ts | 3 + src/assets/shader/lighting/BxDF_frag.ts | 3 + src/assets/shader/lighting/Hair_frag.ts | 3 + .../shader/lighting/IESProfiles_frag.ts | 3 + .../lighting/IrradianceVolumeData_frag.ts | 5 +- src/assets/shader/lighting/Irradiance_frag.ts | 3 + .../shader/lighting/LightingFunction_frag.ts | 3 + src/assets/shader/lighting/UnLit_frag.ts | 3 + src/assets/shader/materials/ColorLitShader.ts | 3 + src/assets/shader/materials/GIProbeShader.ts | 3 + src/assets/shader/materials/GlassShader.ts | 3 + src/assets/shader/materials/Hair_shader.ts | 6 + src/assets/shader/materials/Lambert_shader.ts | 3 + src/assets/shader/materials/Lit_shader.ts | 3 + src/assets/shader/materials/OutlinePass.ts | 3 + src/assets/shader/materials/PBRLItShader.ts | 3 + .../shader/materials/PBRLitSSSShader.ts | 3 + src/assets/shader/materials/PavementShader.ts | 3 + .../shader/materials/PointShadowDebug.ts | 3 + .../materials/ReflectionShader_shader.ts | 3 + src/assets/shader/materials/UnLit.ts | 3 + .../shader/materials/UnLitTextureArray.ts | 3 + .../materials/program/BxdfDebug_frag.ts | 3 + .../materials/program/Clearcoat_frag.ts | 7 +- .../materials/program/ClusterDebug_frag.ts | 3 + .../materials/program/NormalMap_frag.ts | 3 + .../materials/program/ShadowMapping_frag.ts | 3 + .../materials/uniforms/MaterialUniform.ts | 3 + .../uniforms/PhysicMaterialUniform_frag.ts | 3 + .../uniforms/UnLitMaterialUniform_frag.ts | 3 + .../materials/uniforms/VideoUniform_frag.ts | 3 + src/assets/shader/math/FastMathShader.ts | 3 + src/assets/shader/post/FXAAShader.ts | 3 + src/assets/shader/post/GlobalFog_shader.ts | 3 + src/assets/shader/quad/Quad_shader.ts | 22 +- src/assets/shader/sky/CubeSky_Shader.ts | 3 + src/assets/shader/utils/BitUtil.ts | 3 + src/assets/shader/utils/ColorUtil.ts | 3 + src/assets/shader/utils/GenerayRandomDir.ts | 5 +- src/index.ts | 1 - .../parser/gltf/GLTFSubParserConverter.ts | 3 - .../gltf/extends/KHR_materials_clearcoat.ts | 8 +- .../parser/prefab/mats/shader/StandShader.ts | 1 - src/materials/LitMaterial.ts | 4 - src/materials/MaterialRegister.ts | 1 - src/materials/PhysicMaterial.ts | 516 ------------------ 139 files changed, 445 insertions(+), 562 deletions(-) delete mode 100644 src/materials/PhysicMaterial.ts diff --git a/packages/debug/GUIHelp.ts b/packages/debug/GUIHelp.ts index 841bd6ec..5722ca25 100644 --- a/packages/debug/GUIHelp.ts +++ b/packages/debug/GUIHelp.ts @@ -181,6 +181,7 @@ class _GUIHelp { } } } + /** * @internal */ diff --git a/packages/effect/grass/shader/GrassBaseShader.ts b/packages/effect/grass/shader/GrassBaseShader.ts index 820d7844..4f963b85 100644 --- a/packages/effect/grass/shader/GrassBaseShader.ts +++ b/packages/effect/grass/shader/GrassBaseShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GrassBaseShader = /*wgsl*/ ` #include "GlobalUniform" #include "FragmentOutput" diff --git a/packages/effect/grass/shader/GrassCastShadowShader.ts b/packages/effect/grass/shader/GrassCastShadowShader.ts index ebe89e51..5929a65c 100644 --- a/packages/effect/grass/shader/GrassCastShadowShader.ts +++ b/packages/effect/grass/shader/GrassCastShadowShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GrassCastShadowShader = /* wgsl */` #include "WorldMatrixUniform" #include "GrassVertexAttributeShader" diff --git a/packages/effect/grass/shader/GrassShader.ts b/packages/effect/grass/shader/GrassShader.ts index dbffd135..a5c8a8a7 100644 --- a/packages/effect/grass/shader/GrassShader.ts +++ b/packages/effect/grass/shader/GrassShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GrassShader = /* wgsl */` #include "WorldMatrixUniform" #include "GrassVertexAttributeShader" diff --git a/packages/effect/grass/shader/GrassVertexAttributeShader.ts b/packages/effect/grass/shader/GrassVertexAttributeShader.ts index 6b457dc8..54b9f5ed 100644 --- a/packages/effect/grass/shader/GrassVertexAttributeShader.ts +++ b/packages/effect/grass/shader/GrassVertexAttributeShader.ts @@ -1,4 +1,7 @@ +/** + * @internal + */ export let GrassVertexAttributeShader: string = /*wgsl*/ ` #include "WorldMatrixUniform" struct VertexAttributes{ diff --git a/packages/graphic/compute/graphic3d/Graphic3DShader.ts b/packages/graphic/compute/graphic3d/Graphic3DShader.ts index c2105ffd..23b02765 100644 --- a/packages/graphic/compute/graphic3d/Graphic3DShader.ts +++ b/packages/graphic/compute/graphic3d/Graphic3DShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Graphic3DShader: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicDecCompute.ts b/packages/graphic/compute/graphic3d/GraphicDecCompute.ts index d06940d3..6bdfbeb2 100644 --- a/packages/graphic/compute/graphic3d/GraphicDecCompute.ts +++ b/packages/graphic/compute/graphic3d/GraphicDecCompute.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let graphicDecCompute = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts b/packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts index 16b71255..754ff0b2 100644 --- a/packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts +++ b/packages/graphic/compute/graphic3d/GraphicDynamicCompute.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let graphicDynamicCompute = (subCode: string) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicFaceComput2.ts b/packages/graphic/compute/graphic3d/GraphicFaceComput2.ts index 31a9191e..b9e776ad 100644 --- a/packages/graphic/compute/graphic3d/GraphicFaceComput2.ts +++ b/packages/graphic/compute/graphic3d/GraphicFaceComput2.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let graphicFaceCompute2 = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicFaceCompute.ts b/packages/graphic/compute/graphic3d/GraphicFaceCompute.ts index 721bdc00..dab00e65 100644 --- a/packages/graphic/compute/graphic3d/GraphicFaceCompute.ts +++ b/packages/graphic/compute/graphic3d/GraphicFaceCompute.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let graphicFaceCompute = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts b/packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts index 04de8d71..4148b7ce 100644 --- a/packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts +++ b/packages/graphic/compute/graphic3d/GraphicFaceCompute3.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GraphicFaceCompute3 = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicLineCompute.ts b/packages/graphic/compute/graphic3d/GraphicLineCompute.ts index 906852ae..252534e5 100644 --- a/packages/graphic/compute/graphic3d/GraphicLineCompute.ts +++ b/packages/graphic/compute/graphic3d/GraphicLineCompute.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GraphicLineCompute = () => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicTrailCompute.ts b/packages/graphic/compute/graphic3d/GraphicTrailCompute.ts index 4bb75805..68b76a03 100644 --- a/packages/graphic/compute/graphic3d/GraphicTrailCompute.ts +++ b/packages/graphic/compute/graphic3d/GraphicTrailCompute.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let graphicTrailCompute = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts b/packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts index 51b0e321..77f1cdbc 100644 --- a/packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts +++ b/packages/graphic/compute/graphic3d/GraphicTrailCompute2.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GraphicTrailCompute2 = (segmentCode: number) => { let code = /*wgsl*/` #include "GlobalUniform" diff --git a/packages/graphic/compute/grass/GrassAnimCompute_cs.ts b/packages/graphic/compute/grass/GrassAnimCompute_cs.ts index de13330a..26f89fa8 100644 --- a/packages/graphic/compute/grass/GrassAnimCompute_cs.ts +++ b/packages/graphic/compute/grass/GrassAnimCompute_cs.ts @@ -1,2 +1,5 @@ +/** + * @internal + */ export let GrassAnimCompute_cs = /*wgsl*/` ` \ No newline at end of file diff --git a/packages/graphic/compute/grass/GrassGeometryCompute_cs.ts b/packages/graphic/compute/grass/GrassGeometryCompute_cs.ts index 167f8655..d923d49e 100644 --- a/packages/graphic/compute/grass/GrassGeometryCompute_cs.ts +++ b/packages/graphic/compute/grass/GrassGeometryCompute_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GrassGeometryCompute_cs = /*wgsl*/` struct GrassNode { grassCount: f32, diff --git a/packages/graphic/compute/shape3d/CircleShape3DCode_cs.ts b/packages/graphic/compute/shape3d/CircleShape3DCode_cs.ts index b1057974..e5ed6136 100644 --- a/packages/graphic/compute/shape3d/CircleShape3DCode_cs.ts +++ b/packages/graphic/compute/shape3d/CircleShape3DCode_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let CircleShape3DCode_cs = /*wgsl*/` struct CircleShape3D { diff --git a/packages/graphic/compute/shape3d/EllipseShape3DCode_cs.ts b/packages/graphic/compute/shape3d/EllipseShape3DCode_cs.ts index 3df69afe..d28b543f 100644 --- a/packages/graphic/compute/shape3d/EllipseShape3DCode_cs.ts +++ b/packages/graphic/compute/shape3d/EllipseShape3DCode_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let EllipseShape3DCode_cs = /*wgsl*/` struct EllipseShape3D { diff --git a/packages/graphic/compute/shape3d/Path2DShape3DCode_cs.ts b/packages/graphic/compute/shape3d/Path2DShape3DCode_cs.ts index 197b5039..85c240a0 100644 --- a/packages/graphic/compute/shape3d/Path2DShape3DCode_cs.ts +++ b/packages/graphic/compute/shape3d/Path2DShape3DCode_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Path2DShape3DCode_cs = /*wgsl*/` struct Path2DShape3D { diff --git a/packages/graphic/compute/shape3d/Path3DShape3DCode_cs.ts b/packages/graphic/compute/shape3d/Path3DShape3DCode_cs.ts index c75bbd82..948b8f9c 100644 --- a/packages/graphic/compute/shape3d/Path3DShape3DCode_cs.ts +++ b/packages/graphic/compute/shape3d/Path3DShape3DCode_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Path3DShape3DCode_cs = /*wgsl*/` struct Path3DShape3D { diff --git a/packages/graphic/compute/shape3d/RoundRectShape3DCode_cs.ts b/packages/graphic/compute/shape3d/RoundRectShape3DCode_cs.ts index 3b65095f..c287f9d9 100644 --- a/packages/graphic/compute/shape3d/RoundRectShape3DCode_cs.ts +++ b/packages/graphic/compute/shape3d/RoundRectShape3DCode_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let RoundRectShape3DCode_cs = /*wgsl*/` struct RoundRectShape3D { diff --git a/packages/graphic/compute/shape3d/Shape3DCommonCode_cs.ts b/packages/graphic/compute/shape3d/Shape3DCommonCode_cs.ts index 1bb6ce90..d4d55f61 100644 --- a/packages/graphic/compute/shape3d/Shape3DCommonCode_cs.ts +++ b/packages/graphic/compute/shape3d/Shape3DCommonCode_cs.ts @@ -4,6 +4,9 @@ import { Path2DShape3DCode_cs } from "./Path2DShape3DCode_cs"; import { Path3DShape3DCode_cs } from "./Path3DShape3DCode_cs"; import { RoundRectShape3DCode_cs } from "./RoundRectShape3DCode_cs"; +/** + * @internal + */ export let Shape3DCommonCode_cs = /*wgsl*/` ${CircleShape3DCode_cs} diff --git a/packages/graphic/compute/shape3d/Shape3DKeyPointCompute_cs.ts b/packages/graphic/compute/shape3d/Shape3DKeyPointCompute_cs.ts index 8275318c..dc25ef63 100644 --- a/packages/graphic/compute/shape3d/Shape3DKeyPointCompute_cs.ts +++ b/packages/graphic/compute/shape3d/Shape3DKeyPointCompute_cs.ts @@ -1,5 +1,8 @@ import { Shape3DCommonCode_cs } from "./Shape3DCommonCode_cs"; +/** + * @internal + */ export let Shape3DKeyPointCompute_cs = /*wgsl*/` ${Shape3DCommonCode_cs} diff --git a/packages/graphic/compute/shape3d/Shape3DVertexCompute_cs.ts b/packages/graphic/compute/shape3d/Shape3DVertexCompute_cs.ts index 3ce96b5c..d5ba0509 100644 --- a/packages/graphic/compute/shape3d/Shape3DVertexCompute_cs.ts +++ b/packages/graphic/compute/shape3d/Shape3DVertexCompute_cs.ts @@ -1,5 +1,8 @@ import { Shape3DCommonCode_cs } from "./Shape3DCommonCode_cs"; +/** + * @internal + */ export let Shape3DVertexCompute_cs = /*wgsl*/` ${Shape3DCommonCode_cs} diff --git a/packages/graphic/compute/shape3d/Shape3DVertexFillZero_cs.ts b/packages/graphic/compute/shape3d/Shape3DVertexFillZero_cs.ts index 0085a7f4..52eb5ea3 100644 --- a/packages/graphic/compute/shape3d/Shape3DVertexFillZero_cs.ts +++ b/packages/graphic/compute/shape3d/Shape3DVertexFillZero_cs.ts @@ -1,5 +1,8 @@ import { Shape3DCommonCode_cs } from "./Shape3DCommonCode_cs"; +/** + * @internal + */ export let Shape3DVertexFillZero_cs = /*wgsl*/` ${Shape3DCommonCode_cs} diff --git a/packages/media-extention/ChromaKeyShader.ts b/packages/media-extention/ChromaKeyShader.ts index dcf5a9fa..2e7db320 100644 --- a/packages/media-extention/ChromaKeyShader.ts +++ b/packages/media-extention/ChromaKeyShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ChromaKeyShader = /*wgsl*/` #include "Common_vert" #include "Common_frag" diff --git a/packages/media-extention/VideoShader.ts b/packages/media-extention/VideoShader.ts index ed8d6cc5..0171b270 100644 --- a/packages/media-extention/VideoShader.ts +++ b/packages/media-extention/VideoShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let VideoShader = /*wgsl*/` #include "Common_vert" #include "Common_frag" diff --git a/packages/particle/shader/ParticleRenderShader.ts b/packages/particle/shader/ParticleRenderShader.ts index eddfd362..f2bbc338 100644 --- a/packages/particle/shader/ParticleRenderShader.ts +++ b/packages/particle/shader/ParticleRenderShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ParticleRenderShader = /* wgsl */ ` #include "Common_vert" #include "Common_frag" diff --git a/packages/particle/shader/Particle_Mass_UnLight_shader.ts b/packages/particle/shader/Particle_Mass_UnLight_shader.ts index 12a41c2e..781f42fe 100644 --- a/packages/particle/shader/Particle_Mass_UnLight_shader.ts +++ b/packages/particle/shader/Particle_Mass_UnLight_shader.ts @@ -101,6 +101,7 @@ fn main( return VertexOutput(Vertex_Uv ,vWorldPos.xyz,vWorldNormal, position ); } `; + /** * @internal */ diff --git a/packages/particle/shader/compute_density_pressure_compute.ts b/packages/particle/shader/compute_density_pressure_compute.ts index 8ae509fd..b96c0bf1 100644 --- a/packages/particle/shader/compute_density_pressure_compute.ts +++ b/packages/particle/shader/compute_density_pressure_compute.ts @@ -1,4 +1,5 @@ import { FastMath_shader } from './FastMath'; + /** * @internal */ diff --git a/packages/physics/Physics.ts b/packages/physics/Physics.ts index 95c714f7..115310f4 100644 --- a/packages/physics/Physics.ts +++ b/packages/physics/Physics.ts @@ -169,5 +169,8 @@ class _Physics { * ``` * @group Plugin */ +/** + * @internal + */ export let Physics = new _Physics(); export {Ammo} diff --git a/src/assets/shader/anim/SkeletonAnimation_shader.ts b/src/assets/shader/anim/SkeletonAnimation_shader.ts index 0ca8faa5..c210e427 100644 --- a/src/assets/shader/anim/SkeletonAnimation_shader.ts +++ b/src/assets/shader/anim/SkeletonAnimation_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export class SkeletonAnimation_shader { public static groupBindingAndFunctions(beginGroup: number, beginBinding: number) { return /* wgsl */ ` diff --git a/src/assets/shader/cluster/ClusterBoundsSource_cs.ts b/src/assets/shader/cluster/ClusterBoundsSource_cs.ts index da7e0cca..e634bf09 100644 --- a/src/assets/shader/cluster/ClusterBoundsSource_cs.ts +++ b/src/assets/shader/cluster/ClusterBoundsSource_cs.ts @@ -1,5 +1,8 @@ import { ClusterConfig } from "../../../gfx/renderJob/passRenderer/cluster/ClusterConfig"; +/** + * @internal + */ export let ClusterBoundsSource_cs: string = /* wgsl */` #include "GlobalUniform" diff --git a/src/assets/shader/cluster/ClusterLighting_cs.ts b/src/assets/shader/cluster/ClusterLighting_cs.ts index 5b1ef22a..af3fef1f 100644 --- a/src/assets/shader/cluster/ClusterLighting_cs.ts +++ b/src/assets/shader/cluster/ClusterLighting_cs.ts @@ -1,5 +1,8 @@ import { ClusterConfig } from "../../../gfx/renderJob/passRenderer/cluster/ClusterConfig"; +/** + * @internal + */ export let ClusterLighting_cs: string = /*wgsl*/` #include "GlobalUniform" diff --git a/src/assets/shader/compute/BLUR_CsShader.ts b/src/assets/shader/compute/BLUR_CsShader.ts index 072dd06e..d69bdcc0 100644 --- a/src/assets/shader/compute/BLUR_CsShader.ts +++ b/src/assets/shader/compute/BLUR_CsShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BLUR_CsShader: string = /* wgsl */` #include "GlobalUniform" diff --git a/src/assets/shader/compute/BRDFLUT.ts b/src/assets/shader/compute/BRDFLUT.ts index e40760bc..e15faa66 100644 --- a/src/assets/shader/compute/BRDFLUT.ts +++ b/src/assets/shader/compute/BRDFLUT.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let BRDFLUT: string = /*wgsl*/ ` varPI: f32 = 3.141592653589793; diff --git a/src/assets/shader/compute/BloomEffect_cs.ts b/src/assets/shader/compute/BloomEffect_cs.ts index f42aa013..f26eb34e 100644 --- a/src/assets/shader/compute/BloomEffect_cs.ts +++ b/src/assets/shader/compute/BloomEffect_cs.ts @@ -1,5 +1,7 @@ import { ColorUtil } from "../utils/ColorUtil"; - +/** + * @internal + */ let BloomCfg = /*wgsl*/ ` struct BloomCfg{ downSampleStep: f32, @@ -25,7 +27,9 @@ let CalcUV_01 = /*wgsl*/ ` ` //_______________calc weight - +/** + * @internal + */ let GaussWeight2D: string = /*wgsl*/ ` fn GaussWeight2D(x:f32, y:f32, sigma:f32) -> f32 { @@ -37,11 +41,12 @@ fn GaussWeight2D(x:f32, y:f32, sigma:f32) -> f32 return pow(E, a) / (2.0 * PI * sigma_2); } ` - +/** + * @internal + */ let GaussBlur = function (GaussNxN: string, inTex: string, inTexSampler: string) { var code: string = /*wgsl*/ ` - - + fn ${GaussNxN}(uv:vec2, n:i32, stride:vec2, sigma:f32) -> vec3 { var color = vec3(0.0); @@ -69,7 +74,9 @@ let GaussBlur = function (GaussNxN: string, inTex: string, inTexSampler: string) //________________________pixel filter - +/** + * @internal + */ export let threshold: string = /*wgsl*/ ` ${ColorUtil} ${BloomCfg} @@ -105,7 +112,9 @@ fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_inv ` //________________________down sample - +/** + * @internal + */ export let downSample: string = /*wgsl*/ ` ${BloomCfg} @@ -139,6 +148,9 @@ fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_inv //__________________________up sample +/** + * @internal + */ export let upSample = /*wgsl*/ ` ${BloomCfg} @@ -180,6 +192,9 @@ fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_inv //__________________________blend +/** + * @internal + */ export let post = /*wgsl*/ ` ${ColorUtil} ${BloomCfg} diff --git a/src/assets/shader/compute/BlurEffectCreator_cs.ts b/src/assets/shader/compute/BlurEffectCreator_cs.ts index def9c40d..f47c8275 100644 --- a/src/assets/shader/compute/BlurEffectCreator_cs.ts +++ b/src/assets/shader/compute/BlurEffectCreator_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BlurEffectCreatorSample_cs: string = /*wgsl*/ ` struct ImageSize { srcWidth: i32, @@ -20,6 +23,9 @@ export let BlurEffectCreatorSample_cs: string = /*wgsl*/ ` } ` +/** + * @internal + */ export let BlurEffectCreatorBlur_cs: string = /*wgsl*/ ` struct ImageSize { srcWidth: i32, diff --git a/src/assets/shader/compute/DDGIIrradiance_Cs.ts b/src/assets/shader/compute/DDGIIrradiance_Cs.ts index ae97f6c1..4b37e8ac 100644 --- a/src/assets/shader/compute/DDGIIrradiance_Cs.ts +++ b/src/assets/shader/compute/DDGIIrradiance_Cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let DDGIIrradiance_shader = /*wgsl*/` #include "GenerayRandomDir" #include "MathShader" diff --git a/src/assets/shader/compute/DDGILighting_CSShader.ts b/src/assets/shader/compute/DDGILighting_CSShader.ts index 06864683..1d6593aa 100644 --- a/src/assets/shader/compute/DDGILighting_CSShader.ts +++ b/src/assets/shader/compute/DDGILighting_CSShader.ts @@ -1,5 +1,8 @@ import { CSM } from "../../../core/csm/CSM"; +/** + * @internal + */ export let DDGILighting_shader = /*wgsl*/` var PI: f32 = 3.14159265359; diff --git a/src/assets/shader/compute/DepthOfView_cs.ts b/src/assets/shader/compute/DepthOfView_cs.ts index e663bb31..bbe7d04c 100644 --- a/src/assets/shader/compute/DepthOfView_cs.ts +++ b/src/assets/shader/compute/DepthOfView_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let DepthOfView_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "GBufferStand" diff --git a/src/assets/shader/compute/ErpImage2CubeMapCreateCube_cs.ts b/src/assets/shader/compute/ErpImage2CubeMapCreateCube_cs.ts index d08b2e01..e66fc946 100644 --- a/src/assets/shader/compute/ErpImage2CubeMapCreateCube_cs.ts +++ b/src/assets/shader/compute/ErpImage2CubeMapCreateCube_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ErpImage2CubeMapCreateCube_cs: string = /*wgsl*/ ` struct ImageSize { srcWidth : i32, diff --git a/src/assets/shader/compute/ErpImage2CubeMapRgbe2rgba_cs.ts b/src/assets/shader/compute/ErpImage2CubeMapRgbe2rgba_cs.ts index 4c6f676f..571a58e2 100644 --- a/src/assets/shader/compute/ErpImage2CubeMapRgbe2rgba_cs.ts +++ b/src/assets/shader/compute/ErpImage2CubeMapRgbe2rgba_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ErpImage2CubeMapRgbe2rgba_cs: string = /*wgsl*/ ` struct ImageSize { srcWidth : i32, diff --git a/src/assets/shader/compute/GTAO_cs.ts b/src/assets/shader/compute/GTAO_cs.ts index a3b7f883..879f5261 100644 --- a/src/assets/shader/compute/GTAO_cs.ts +++ b/src/assets/shader/compute/GTAO_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GTAO_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "GBufferStand" diff --git a/src/assets/shader/compute/GodRay_cs.ts b/src/assets/shader/compute/GodRay_cs.ts index 68b8ef8f..60295f0e 100644 --- a/src/assets/shader/compute/GodRay_cs.ts +++ b/src/assets/shader/compute/GodRay_cs.ts @@ -1,5 +1,8 @@ import { CSM } from "../../../core/csm/CSM"; +/** + * @internal + */ export let GodRay_cs: string = /*wgsl*/ ` #include "GlobalUniform" diff --git a/src/assets/shader/compute/IBLEnvMapCreator_cs.ts b/src/assets/shader/compute/IBLEnvMapCreator_cs.ts index d8618870..4cbbeced 100644 --- a/src/assets/shader/compute/IBLEnvMapCreator_cs.ts +++ b/src/assets/shader/compute/IBLEnvMapCreator_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let IBLEnvMapCreator_cs: string = /*wgsl*/ ` struct ImageSize { srcWidth : i32, diff --git a/src/assets/shader/compute/MergeRGBA_cs.ts b/src/assets/shader/compute/MergeRGBA_cs.ts index 023389af..30745ede 100644 --- a/src/assets/shader/compute/MergeRGBA_cs.ts +++ b/src/assets/shader/compute/MergeRGBA_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let MergeRGBA_cs: string = /*wgsl*/ ` @group(0) @binding(0) var textureR : texture_2d; @group(0) @binding(1) var textureG : texture_2d; diff --git a/src/assets/shader/compute/MultiBouncePass_cs.ts b/src/assets/shader/compute/MultiBouncePass_cs.ts index bf44b2db..d2eace02 100644 --- a/src/assets/shader/compute/MultiBouncePass_cs.ts +++ b/src/assets/shader/compute/MultiBouncePass_cs.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let MultiBouncePass_cs: string = /*wgsl*/ ` #include "MathShader" #include "IrradianceVolumeData_frag" diff --git a/src/assets/shader/compute/OutLineBlendColor_cs.ts b/src/assets/shader/compute/OutLineBlendColor_cs.ts index 4661a55d..09ed30e1 100644 --- a/src/assets/shader/compute/OutLineBlendColor_cs.ts +++ b/src/assets/shader/compute/OutLineBlendColor_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let OutLineBlendColor_cs: string = /*wgsl*/ ` struct OutlineSettingData{ strength: f32, diff --git a/src/assets/shader/compute/OutlineCalcOutline_cs.ts b/src/assets/shader/compute/OutlineCalcOutline_cs.ts index 74979c42..a04f3f39 100644 --- a/src/assets/shader/compute/OutlineCalcOutline_cs.ts +++ b/src/assets/shader/compute/OutlineCalcOutline_cs.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let OutlineCalcOutline_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "GBufferStand" diff --git a/src/assets/shader/compute/Outline_cs.ts b/src/assets/shader/compute/Outline_cs.ts index e74b83a7..e9c9a978 100644 --- a/src/assets/shader/compute/Outline_cs.ts +++ b/src/assets/shader/compute/Outline_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Outline_cs: string = /*wgsl*/ ` struct OutlineSettingData{ strength: f32, diff --git a/src/assets/shader/compute/Picker_cs.ts b/src/assets/shader/compute/Picker_cs.ts index f746fe1d..c8f2feea 100644 --- a/src/assets/shader/compute/Picker_cs.ts +++ b/src/assets/shader/compute/Picker_cs.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let Picker_cs: string = /*wgsl*/ ` #include "GlobalUniform" diff --git a/src/assets/shader/compute/PreFilteredEnvironment_cs copy.ts b/src/assets/shader/compute/PreFilteredEnvironment_cs copy.ts index f311bf50..3cd4dc0b 100644 --- a/src/assets/shader/compute/PreFilteredEnvironment_cs copy.ts +++ b/src/assets/shader/compute/PreFilteredEnvironment_cs copy.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PreFilteredEnvironment_cs2: string = /*wgsl*/ ` // input reflection buffer texture // sample prefiltered cube uv texture diff --git a/src/assets/shader/compute/PreFilteredEnvironment_cs.ts b/src/assets/shader/compute/PreFilteredEnvironment_cs.ts index dcae65a8..c1a276d7 100644 --- a/src/assets/shader/compute/PreFilteredEnvironment_cs.ts +++ b/src/assets/shader/compute/PreFilteredEnvironment_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PreFilteredEnvironment_cs: string = /*wgsl*/ ` // input reflection buffer texture // sample prefiltered cube uv texture diff --git a/src/assets/shader/compute/PreIntegratedLut.ts b/src/assets/shader/compute/PreIntegratedLut.ts index e3676e29..5aee9872 100644 --- a/src/assets/shader/compute/PreIntegratedLut.ts +++ b/src/assets/shader/compute/PreIntegratedLut.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let PreIntegratedLut: string = /*wgsl*/ ` varPI: f32 = 3.141592653589793; diff --git a/src/assets/shader/compute/SSAO_cs.ts b/src/assets/shader/compute/SSAO_cs.ts index 60864622..6e719311 100644 --- a/src/assets/shader/compute/SSAO_cs.ts +++ b/src/assets/shader/compute/SSAO_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SSAO_cs: string = /*wgsl*/ ` #include "GlobalUniform" struct UniformData { diff --git a/src/assets/shader/compute/SSGI2_cs.ts b/src/assets/shader/compute/SSGI2_cs.ts index 13d1fe46..bb2953fb 100644 --- a/src/assets/shader/compute/SSGI2_cs.ts +++ b/src/assets/shader/compute/SSGI2_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SSGI2_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "MathShader" diff --git a/src/assets/shader/compute/SSR_BlendColor_cs.ts b/src/assets/shader/compute/SSR_BlendColor_cs.ts index c10d8117..392c2d5f 100644 --- a/src/assets/shader/compute/SSR_BlendColor_cs.ts +++ b/src/assets/shader/compute/SSR_BlendColor_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SSR_BlendColor_cs: string = /*wgsl*/ ` #include 'GlobalUniform' diff --git a/src/assets/shader/compute/SSR_IS_cs.ts b/src/assets/shader/compute/SSR_IS_cs.ts index 10422eb5..dff18e49 100644 --- a/src/assets/shader/compute/SSR_IS_cs.ts +++ b/src/assets/shader/compute/SSR_IS_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SSR_IS_cs: string = /*wgsl*/ ` #include 'BitUtil' diff --git a/src/assets/shader/compute/SSR_RayTrace_cs.ts b/src/assets/shader/compute/SSR_RayTrace_cs.ts index 742e865e..c7f0781f 100644 --- a/src/assets/shader/compute/SSR_RayTrace_cs.ts +++ b/src/assets/shader/compute/SSR_RayTrace_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SSR_RayTrace_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "MathShader" diff --git a/src/assets/shader/compute/TAACopyTex_cs.ts b/src/assets/shader/compute/TAACopyTex_cs.ts index 913135b7..7f2e96cf 100644 --- a/src/assets/shader/compute/TAACopyTex_cs.ts +++ b/src/assets/shader/compute/TAACopyTex_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let TAACopyTex_cs: string = /*wgsl*/ ` @group(0) @binding(0) var preColor : array>; @group(0) @binding(1) var preColorTex : texture_storage_2d; diff --git a/src/assets/shader/compute/TAASharpTex_cs.ts b/src/assets/shader/compute/TAASharpTex_cs.ts index a9a797b8..06debe74 100644 --- a/src/assets/shader/compute/TAASharpTex_cs.ts +++ b/src/assets/shader/compute/TAASharpTex_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let TAASharpTex_cs: string = /*wgsl*/ ` struct TAAData{ preProjMatrix: mat4x4, diff --git a/src/assets/shader/compute/TAA_cs.ts b/src/assets/shader/compute/TAA_cs.ts index 19dd04e3..dffe4993 100644 --- a/src/assets/shader/compute/TAA_cs.ts +++ b/src/assets/shader/compute/TAA_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let TAA_cs: string = /*wgsl*/ ` #include "GlobalUniform" #include "GBufferStand" diff --git a/src/assets/shader/compute/utils/Combine_cs.ts b/src/assets/shader/compute/utils/Combine_cs.ts index 2fc71ac8..4684b032 100644 --- a/src/assets/shader/compute/utils/Combine_cs.ts +++ b/src/assets/shader/compute/utils/Combine_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Combine_cs = /* wgsl */` #include "GlobalUniform" #include "BitUtil" diff --git a/src/assets/shader/compute/utils/Denoising_cs.ts b/src/assets/shader/compute/utils/Denoising_cs.ts index d43d49ed..ee97d213 100644 --- a/src/assets/shader/compute/utils/Denoising_cs.ts +++ b/src/assets/shader/compute/utils/Denoising_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Denoising_cs = /* wgsl */` // #include "GlobalUniform" diff --git a/src/assets/shader/compute/utils/TestComputeLoadBuffer.ts b/src/assets/shader/compute/utils/TestComputeLoadBuffer.ts index 84a2a323..9e178290 100644 --- a/src/assets/shader/compute/utils/TestComputeLoadBuffer.ts +++ b/src/assets/shader/compute/utils/TestComputeLoadBuffer.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let TestComputeLoadBuffer = /* wgsl */` #include "GlobalUniform" #include "MathShader" diff --git a/src/assets/shader/compute/utils/tw.ts b/src/assets/shader/compute/utils/tw.ts index 9981b845..423d2542 100644 --- a/src/assets/shader/compute/utils/tw.ts +++ b/src/assets/shader/compute/utils/tw.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let tw = /* wgsl */` @group(0) @binding(1) var inputTexture : texture_2d; diff --git a/src/assets/shader/core/base/Common_frag.ts b/src/assets/shader/core/base/Common_frag.ts index 8b7c5e87..b65957e2 100644 --- a/src/assets/shader/core/base/Common_frag.ts +++ b/src/assets/shader/core/base/Common_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Common_frag: string = /*wgsl*/ ` #include "GlobalUniform" #include "FragmentVarying" diff --git a/src/assets/shader/core/base/Common_vert.ts b/src/assets/shader/core/base/Common_vert.ts index e456d252..bca64ece 100644 --- a/src/assets/shader/core/base/Common_vert.ts +++ b/src/assets/shader/core/base/Common_vert.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Common_vert: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "VertexAttributes_vert" diff --git a/src/assets/shader/core/common/BrdfLut_frag.ts b/src/assets/shader/core/common/BrdfLut_frag.ts index 392920ce..2aca59bb 100644 --- a/src/assets/shader/core/common/BrdfLut_frag.ts +++ b/src/assets/shader/core/common/BrdfLut_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BrdfLut_frag: string = /*wgsl*/ ` @group(1) @binding(auto) var brdflutMapSampler: sampler; diff --git a/src/assets/shader/core/common/EnvMap_frag.ts b/src/assets/shader/core/common/EnvMap_frag.ts index aa4e0067..6ae9749e 100644 --- a/src/assets/shader/core/common/EnvMap_frag.ts +++ b/src/assets/shader/core/common/EnvMap_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let EnvMap_frag: string = /*wgsl*/ ` @group(1) @binding(auto) var prefilterMapSampler: sampler; diff --git a/src/assets/shader/core/common/GBufferStand.ts b/src/assets/shader/core/common/GBufferStand.ts index df1e83a2..953574cf 100644 --- a/src/assets/shader/core/common/GBufferStand.ts +++ b/src/assets/shader/core/common/GBufferStand.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GBufferStand = /* wgsl */ ` #include "MathShader" #include "FastMathShader" diff --git a/src/assets/shader/core/common/GlobalUniform.ts b/src/assets/shader/core/common/GlobalUniform.ts index 59cd7748..d2b25a94 100644 --- a/src/assets/shader/core/common/GlobalUniform.ts +++ b/src/assets/shader/core/common/GlobalUniform.ts @@ -1,5 +1,8 @@ import { CSM } from "../../../../core/csm/CSM"; +/** + * @internal + */ export let GlobalUniform: string = /*wgsl*/ ` #include "MathShader" diff --git a/src/assets/shader/core/common/InstanceUniform.ts b/src/assets/shader/core/common/InstanceUniform.ts index d5c876e8..4c756843 100644 --- a/src/assets/shader/core/common/InstanceUniform.ts +++ b/src/assets/shader/core/common/InstanceUniform.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let InstanceUniform: string = /*wgsl*/ ` #if USE_INSTANCEDRAW struct InstanceUniform { diff --git a/src/assets/shader/core/common/SHCommon_frag.ts b/src/assets/shader/core/common/SHCommon_frag.ts index 4062e68e..60d8c3e9 100644 --- a/src/assets/shader/core/common/SHCommon_frag.ts +++ b/src/assets/shader/core/common/SHCommon_frag.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let SHCommon_frag: string = /*wgsl*/ ` fn Y0(v : vec3f ) -> f32 { diff --git a/src/assets/shader/core/common/WorldMatrixUniform.ts b/src/assets/shader/core/common/WorldMatrixUniform.ts index 12368208..36914c55 100644 --- a/src/assets/shader/core/common/WorldMatrixUniform.ts +++ b/src/assets/shader/core/common/WorldMatrixUniform.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let WorldMatrixUniform: string = /*wgsl*/ ` struct Uniforms { matrix : array> diff --git a/src/assets/shader/core/inline/Inline_vert.ts b/src/assets/shader/core/inline/Inline_vert.ts index 9ccf7d91..87fd714f 100644 --- a/src/assets/shader/core/inline/Inline_vert.ts +++ b/src/assets/shader/core/inline/Inline_vert.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Inline_vert: string = /*wgsl*/ ` #include "MathShader" #include "FastMathShader" diff --git a/src/assets/shader/core/pass/CastShadow_pass.ts b/src/assets/shader/core/pass/CastShadow_pass.ts index 71822271..6b9a31c2 100644 --- a/src/assets/shader/core/pass/CastShadow_pass.ts +++ b/src/assets/shader/core/pass/CastShadow_pass.ts @@ -1,5 +1,9 @@ import { SkeletonAnimation_shader } from "../../anim/SkeletonAnimation_shader"; import { MorphTarget_shader } from "../../../../components/anim/morphAnim/MorphTarget_shader"; + +/** + * @internal + */ export let shadowCastMap_vert: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "GlobalUniform" @@ -109,6 +113,9 @@ fn main(vertex:VertexAttributes) -> VertexOutput { } ` +/** + * @internal + */ export let castPointShadowMap_vert: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "GlobalUniform" @@ -219,6 +226,9 @@ fn main(vertex:VertexAttributes) -> VertexOutput { } ` +/** + * @internal + */ export let shadowCastMap_frag: string = /*wgsl*/ ` #if USE_ALPHACUT @group(1) @binding(0) @@ -259,6 +269,9 @@ export let shadowCastMap_frag: string = /*wgsl*/ ` } ` +/** + * @internal + */ export let directionShadowCastMap_frag: string = /*wgsl*/ ` #if USE_ALPHACUT @group(1) @binding(0) diff --git a/src/assets/shader/core/pass/FrustumCulling_cs.ts b/src/assets/shader/core/pass/FrustumCulling_cs.ts index dd32c4df..9ffffa8c 100644 --- a/src/assets/shader/core/pass/FrustumCulling_cs.ts +++ b/src/assets/shader/core/pass/FrustumCulling_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let FrustumCulling_cs: string = /*wgsl*/ ` #include "GlobalUniform" diff --git a/src/assets/shader/core/pass/GBuffer_pass.ts b/src/assets/shader/core/pass/GBuffer_pass.ts index 5b2eb1ab..ae0843c6 100644 --- a/src/assets/shader/core/pass/GBuffer_pass.ts +++ b/src/assets/shader/core/pass/GBuffer_pass.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GBuffer_pass: string = /*wgsl*/ ` #include "Common_vert" #include "FragmentVarying" diff --git a/src/assets/shader/core/pass/SkyGBuffer_pass.ts b/src/assets/shader/core/pass/SkyGBuffer_pass.ts index 01031709..313d400d 100644 --- a/src/assets/shader/core/pass/SkyGBuffer_pass.ts +++ b/src/assets/shader/core/pass/SkyGBuffer_pass.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let SkyGBuffer_pass: string = /*wgsl*/ ` #include "GlobalUniform" #include "ColorUtil_frag" diff --git a/src/assets/shader/core/pass/ZPassShader_cs.ts b/src/assets/shader/core/pass/ZPassShader_cs.ts index 25de3885..9155dac5 100644 --- a/src/assets/shader/core/pass/ZPassShader_cs.ts +++ b/src/assets/shader/core/pass/ZPassShader_cs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ZPassShader_cs: string = /*wgsl*/ ` @group(0) @binding(0) var visibleBuffer: array; @group(0) @binding(1) var zBufferTexture : texture_2d; diff --git a/src/assets/shader/core/pass/ZPassShader_fs.ts b/src/assets/shader/core/pass/ZPassShader_fs.ts index d441a8d7..340bce95 100644 --- a/src/assets/shader/core/pass/ZPassShader_fs.ts +++ b/src/assets/shader/core/pass/ZPassShader_fs.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ZPassShader_fs: string = /*wgsl*/ ` #include "GlobalUniform" #include "MathShader" diff --git a/src/assets/shader/core/pass/ZPassShader_vs.ts b/src/assets/shader/core/pass/ZPassShader_vs.ts index 6a6ddd86..fa22cb23 100644 --- a/src/assets/shader/core/pass/ZPassShader_vs.ts +++ b/src/assets/shader/core/pass/ZPassShader_vs.ts @@ -1,6 +1,9 @@ import { MorphTarget_shader } from "../../../../components/anim/morphAnim/MorphTarget_shader"; import { SkeletonAnimation_shader } from "../../anim/SkeletonAnimation_shader"; +/** + * @internal + */ export let ZPassShader_vs: string = /*wgsl*/ ` #include "GlobalUniform" #include "MathShader" diff --git a/src/assets/shader/core/struct/ClusterLight.ts b/src/assets/shader/core/struct/ClusterLight.ts index 09741bbc..105933b9 100644 --- a/src/assets/shader/core/struct/ClusterLight.ts +++ b/src/assets/shader/core/struct/ClusterLight.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ClusterLight: string = /*wgsl*/ ` struct LightData { index:f32, diff --git a/src/assets/shader/core/struct/ColorPassFragmentOutput.ts b/src/assets/shader/core/struct/ColorPassFragmentOutput.ts index a8fb31b4..a8bb54a3 100644 --- a/src/assets/shader/core/struct/ColorPassFragmentOutput.ts +++ b/src/assets/shader/core/struct/ColorPassFragmentOutput.ts @@ -1,5 +1,6 @@ - - +/** + * @internal + */ export let ColorPassFragmentOutput: string = /*wgsl*/ ` struct FragmentOutput { @location(auto) color: vec4, diff --git a/src/assets/shader/core/struct/FragmentOutput.ts b/src/assets/shader/core/struct/FragmentOutput.ts index 137683e8..2ded8886 100644 --- a/src/assets/shader/core/struct/FragmentOutput.ts +++ b/src/assets/shader/core/struct/FragmentOutput.ts @@ -1,5 +1,6 @@ - - +/** + * @internal + */ export let FragmentOutput: string = /*wgsl*/ ` #if USE_CASTREFLECTION struct FragmentOutput { diff --git a/src/assets/shader/core/struct/FragmentVarying.ts b/src/assets/shader/core/struct/FragmentVarying.ts index 491235e2..a36a7f87 100644 --- a/src/assets/shader/core/struct/FragmentVarying.ts +++ b/src/assets/shader/core/struct/FragmentVarying.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let FragmentVarying: string = /*wgsl*/ ` struct FragmentVarying { @location(auto) index: f32, diff --git a/src/assets/shader/core/struct/ShadingInput.ts b/src/assets/shader/core/struct/ShadingInput.ts index 764cfd8a..1f4ba2c5 100644 --- a/src/assets/shader/core/struct/ShadingInput.ts +++ b/src/assets/shader/core/struct/ShadingInput.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ShadingInput: string = /*wgsl*/ ` struct ShadingInput{ BaseColor:vec4, diff --git a/src/assets/shader/core/struct/VertexAttributeIndexShader.ts b/src/assets/shader/core/struct/VertexAttributeIndexShader.ts index 98bfaf2f..26cc5fa8 100644 --- a/src/assets/shader/core/struct/VertexAttributeIndexShader.ts +++ b/src/assets/shader/core/struct/VertexAttributeIndexShader.ts @@ -1,4 +1,6 @@ - +/** + * @internal + */ export let VertexAttributeIndexShader: string = /*wgsl*/ ` #include "WorldMatrixUniform" struct VertexAttributes{ diff --git a/src/assets/shader/core/struct/VertexAttributes.ts b/src/assets/shader/core/struct/VertexAttributes.ts index eb251d39..7635ee94 100644 --- a/src/assets/shader/core/struct/VertexAttributes.ts +++ b/src/assets/shader/core/struct/VertexAttributes.ts @@ -1,6 +1,9 @@ import { SkeletonAnimation_shader } from "../../anim/SkeletonAnimation_shader"; import { MorphTarget_shader } from "../../../../components/anim/morphAnim/MorphTarget_shader"; +/** + * @internal + */ export let VertexAttributes_vert: string = /*wgsl*/ ` var PI: f32 = 3.14159265359; #if USE_METAHUMAN diff --git a/src/assets/shader/glsl/Quad_glsl.ts b/src/assets/shader/glsl/Quad_glsl.ts index 7267cddd..5d93ffbb 100644 --- a/src/assets/shader/glsl/Quad_glsl.ts +++ b/src/assets/shader/glsl/Quad_glsl.ts @@ -1,5 +1,6 @@ - - +/** + * @internal + */ export let QuadGlsl_vs = /* wgsl */ ` #include "WorldMatrixUniform" #include "GlobalUniform" diff --git a/src/assets/shader/lighting/BRDF_frag.ts b/src/assets/shader/lighting/BRDF_frag.ts index dd7c08e2..078ee462 100644 --- a/src/assets/shader/lighting/BRDF_frag.ts +++ b/src/assets/shader/lighting/BRDF_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BRDF_frag: string = /*wgsl*/ ` #include "Clearcoat_frag" #include "EnvMap_frag" diff --git a/src/assets/shader/lighting/BsDF_frag.ts b/src/assets/shader/lighting/BsDF_frag.ts index 8bfc8027..0a518c1a 100644 --- a/src/assets/shader/lighting/BsDF_frag.ts +++ b/src/assets/shader/lighting/BsDF_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BsDF_frag: string = /*wgsl*/ ` #include "Clearcoat_frag" #include "BRDF_frag" diff --git a/src/assets/shader/lighting/BxDF_frag.ts b/src/assets/shader/lighting/BxDF_frag.ts index 12531cd5..f1f7af5c 100644 --- a/src/assets/shader/lighting/BxDF_frag.ts +++ b/src/assets/shader/lighting/BxDF_frag.ts @@ -1,4 +1,7 @@ import { SHCommon_frag } from './../core/common/SHCommon_frag'; +/** + * @internal + */ export let BxDF_frag: string = /*wgsl*/ ` #include "Clearcoat_frag" #include "BRDF_frag" diff --git a/src/assets/shader/lighting/Hair_frag.ts b/src/assets/shader/lighting/Hair_frag.ts index e58878eb..151cf674 100644 --- a/src/assets/shader/lighting/Hair_frag.ts +++ b/src/assets/shader/lighting/Hair_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Hair_frag: string = /*wgsl*/ ` #include "BRDF_frag" #include "MathShader" diff --git a/src/assets/shader/lighting/IESProfiles_frag.ts b/src/assets/shader/lighting/IESProfiles_frag.ts index fcf9df90..37982a07 100644 --- a/src/assets/shader/lighting/IESProfiles_frag.ts +++ b/src/assets/shader/lighting/IESProfiles_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let IESProfiles_frag: string = /*wgsl*/ ` #if USE_IES_PROFILE @group(1) @binding(auto) diff --git a/src/assets/shader/lighting/IrradianceVolumeData_frag.ts b/src/assets/shader/lighting/IrradianceVolumeData_frag.ts index 510baa86..7c0ca0ea 100644 --- a/src/assets/shader/lighting/IrradianceVolumeData_frag.ts +++ b/src/assets/shader/lighting/IrradianceVolumeData_frag.ts @@ -1,4 +1,7 @@ -export let IrradianceVolumeData_frag: string = /*wgsl*/ ` +/** + * @internal + */ +export let IrradianceVolumeData_frag: string = /*wgsl*/ ` struct IrradianceVolumeData { //0 orientationIndex:f32, diff --git a/src/assets/shader/lighting/Irradiance_frag.ts b/src/assets/shader/lighting/Irradiance_frag.ts index 477348d0..8e7c0170 100644 --- a/src/assets/shader/lighting/Irradiance_frag.ts +++ b/src/assets/shader/lighting/Irradiance_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Irradiance_frag: string = /*wgsl*/ ` #include "IrradianceVolumeData_frag" struct IrradianceField { diff --git a/src/assets/shader/lighting/LightingFunction_frag.ts b/src/assets/shader/lighting/LightingFunction_frag.ts index 4d516b50..2048bc04 100644 --- a/src/assets/shader/lighting/LightingFunction_frag.ts +++ b/src/assets/shader/lighting/LightingFunction_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let LightingFunction_frag: string = /*wgsl*/ ` #include "BRDF_frag" #include "ClusterLight" diff --git a/src/assets/shader/lighting/UnLit_frag.ts b/src/assets/shader/lighting/UnLit_frag.ts index 53f330f5..743e4091 100644 --- a/src/assets/shader/lighting/UnLit_frag.ts +++ b/src/assets/shader/lighting/UnLit_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let UnLit_frag: string = /*wgsl*/ ` #include "Common_frag" #include "GlobalUniform" diff --git a/src/assets/shader/materials/ColorLitShader.ts b/src/assets/shader/materials/ColorLitShader.ts index 73c70b64..303e4957 100644 --- a/src/assets/shader/materials/ColorLitShader.ts +++ b/src/assets/shader/materials/ColorLitShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ColorLitShader: string = /*wgsl*/` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/GIProbeShader.ts b/src/assets/shader/materials/GIProbeShader.ts index 1d6de14c..a1860d69 100644 --- a/src/assets/shader/materials/GIProbeShader.ts +++ b/src/assets/shader/materials/GIProbeShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GIProbeShader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/GlassShader.ts b/src/assets/shader/materials/GlassShader.ts index 2f213b09..987a4539 100644 --- a/src/assets/shader/materials/GlassShader.ts +++ b/src/assets/shader/materials/GlassShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GlassShader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/Hair_shader.ts b/src/assets/shader/materials/Hair_shader.ts index 9d860039..f46f9312 100644 --- a/src/assets/shader/materials/Hair_shader.ts +++ b/src/assets/shader/materials/Hair_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Hair_shader_op: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" @@ -121,6 +124,9 @@ export let Hair_shader_op: string = /*wgsl*/ ` } ` +/** + * @internal + */ export let Hair_shader_tr: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/Lambert_shader.ts b/src/assets/shader/materials/Lambert_shader.ts index 83795ea0..7271438c 100644 --- a/src/assets/shader/materials/Lambert_shader.ts +++ b/src/assets/shader/materials/Lambert_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Lambert_shader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/Lit_shader.ts b/src/assets/shader/materials/Lit_shader.ts index ceca8bbd..b74ff5dc 100644 --- a/src/assets/shader/materials/Lit_shader.ts +++ b/src/assets/shader/materials/Lit_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let Lit_shader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/OutlinePass.ts b/src/assets/shader/materials/OutlinePass.ts index 44689a12..3081a4f4 100644 --- a/src/assets/shader/materials/OutlinePass.ts +++ b/src/assets/shader/materials/OutlinePass.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let OutlinePass: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/PBRLItShader.ts b/src/assets/shader/materials/PBRLItShader.ts index 6fc2cc35..e46ce022 100644 --- a/src/assets/shader/materials/PBRLItShader.ts +++ b/src/assets/shader/materials/PBRLItShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PBRLItShader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/PBRLitSSSShader.ts b/src/assets/shader/materials/PBRLitSSSShader.ts index 6c08d7df..642f1380 100644 --- a/src/assets/shader/materials/PBRLitSSSShader.ts +++ b/src/assets/shader/materials/PBRLitSSSShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PBRLitSSSShader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/PavementShader.ts b/src/assets/shader/materials/PavementShader.ts index a3a8b752..3f66ca31 100644 --- a/src/assets/shader/materials/PavementShader.ts +++ b/src/assets/shader/materials/PavementShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PavementShader: string = /*wgsl*/` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/PointShadowDebug.ts b/src/assets/shader/materials/PointShadowDebug.ts index e5788317..bc506f23 100644 --- a/src/assets/shader/materials/PointShadowDebug.ts +++ b/src/assets/shader/materials/PointShadowDebug.ts @@ -1,4 +1,7 @@ +/** + * @internal + */ export let PointShadowDebug:string = /*wgsl*/` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/ReflectionShader_shader.ts b/src/assets/shader/materials/ReflectionShader_shader.ts index 47fe593d..ec6c571c 100644 --- a/src/assets/shader/materials/ReflectionShader_shader.ts +++ b/src/assets/shader/materials/ReflectionShader_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ReflectionShader_shader: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/UnLit.ts b/src/assets/shader/materials/UnLit.ts index 77ef33af..05971fd0 100644 --- a/src/assets/shader/materials/UnLit.ts +++ b/src/assets/shader/materials/UnLit.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let UnLit: string = /*wgsl*/ ` #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/UnLitTextureArray.ts b/src/assets/shader/materials/UnLitTextureArray.ts index 8971946b..85b20e41 100644 --- a/src/assets/shader/materials/UnLitTextureArray.ts +++ b/src/assets/shader/materials/UnLitTextureArray.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let UnLitTextureArray: string = /*wgsl*/ ` // #include "Common_vert" #include "Common_frag" diff --git a/src/assets/shader/materials/program/BxdfDebug_frag.ts b/src/assets/shader/materials/program/BxdfDebug_frag.ts index 9c6271b8..39bd35ee 100644 --- a/src/assets/shader/materials/program/BxdfDebug_frag.ts +++ b/src/assets/shader/materials/program/BxdfDebug_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BxdfDebug_frag: string = /*wgsl*/ ` #include "ClusterDebug_frag" diff --git a/src/assets/shader/materials/program/Clearcoat_frag.ts b/src/assets/shader/materials/program/Clearcoat_frag.ts index 8a6ae755..cc268d6c 100644 --- a/src/assets/shader/materials/program/Clearcoat_frag.ts +++ b/src/assets/shader/materials/program/Clearcoat_frag.ts @@ -1,7 +1,6 @@ - - - - +/** + * @internal + */ export let Clearcoat_frag: string = /*wgsl*/ ` #if USE_CLEARCOAT_ROUGHNESS @group(1) @binding(auto) diff --git a/src/assets/shader/materials/program/ClusterDebug_frag.ts b/src/assets/shader/materials/program/ClusterDebug_frag.ts index 77d62ad3..7b9e3c52 100644 --- a/src/assets/shader/materials/program/ClusterDebug_frag.ts +++ b/src/assets/shader/materials/program/ClusterDebug_frag.ts @@ -1,4 +1,7 @@ +/** + * @internal + */ export let ClusterDebug_frag: string = /*wgsl*/` var colorSet : array, 9> = array, 9>( vec3(1.0, 0.0, 0.0), diff --git a/src/assets/shader/materials/program/NormalMap_frag.ts b/src/assets/shader/materials/program/NormalMap_frag.ts index d8795563..e1afea3f 100644 --- a/src/assets/shader/materials/program/NormalMap_frag.ts +++ b/src/assets/shader/materials/program/NormalMap_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let NormalMap_frag: string = /*wgsl*/ ` fn perturbNormal( worldPos:vec3, surf_norm:vec3, mapN:vec3 , normalScale:f32 , face:f32 ) -> vec3 { diff --git a/src/assets/shader/materials/program/ShadowMapping_frag.ts b/src/assets/shader/materials/program/ShadowMapping_frag.ts index 15989190..91f45280 100644 --- a/src/assets/shader/materials/program/ShadowMapping_frag.ts +++ b/src/assets/shader/materials/program/ShadowMapping_frag.ts @@ -1,5 +1,8 @@ import { CSM } from "../../../../core/csm/CSM"; +/** + * @internal + */ export let ShadowMapping_frag: string = /*wgsl*/ ` @group(1) @binding(auto) var shadowMapSampler: sampler; @group(1) @binding(auto) var shadowMap: texture_depth_2d_array; diff --git a/src/assets/shader/materials/uniforms/MaterialUniform.ts b/src/assets/shader/materials/uniforms/MaterialUniform.ts index 03baf7a3..c8189d4b 100644 --- a/src/assets/shader/materials/uniforms/MaterialUniform.ts +++ b/src/assets/shader/materials/uniforms/MaterialUniform.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let MaterialUniform : string = /*wgsl*/ ` struct MaterialUniform{ #if USE_BRDF diff --git a/src/assets/shader/materials/uniforms/PhysicMaterialUniform_frag.ts b/src/assets/shader/materials/uniforms/PhysicMaterialUniform_frag.ts index f0832a79..bcb5000a 100644 --- a/src/assets/shader/materials/uniforms/PhysicMaterialUniform_frag.ts +++ b/src/assets/shader/materials/uniforms/PhysicMaterialUniform_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let PhysicMaterialUniform_frag = /* wgsl */` #if USE_CUSTOMUNIFORM #else diff --git a/src/assets/shader/materials/uniforms/UnLitMaterialUniform_frag.ts b/src/assets/shader/materials/uniforms/UnLitMaterialUniform_frag.ts index 5847578f..bf84674d 100644 --- a/src/assets/shader/materials/uniforms/UnLitMaterialUniform_frag.ts +++ b/src/assets/shader/materials/uniforms/UnLitMaterialUniform_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let UnLitMaterialUniform_frag = /* wgsl */` diff --git a/src/assets/shader/materials/uniforms/VideoUniform_frag.ts b/src/assets/shader/materials/uniforms/VideoUniform_frag.ts index 445e4877..710e1b1f 100644 --- a/src/assets/shader/materials/uniforms/VideoUniform_frag.ts +++ b/src/assets/shader/materials/uniforms/VideoUniform_frag.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let VideoUniform_frag = /* wgsl */` struct MaterialUniform { transformUV1:vec4, diff --git a/src/assets/shader/math/FastMathShader.ts b/src/assets/shader/math/FastMathShader.ts index c3ae62f8..f22b79ce 100644 --- a/src/assets/shader/math/FastMathShader.ts +++ b/src/assets/shader/math/FastMathShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let FastMathShader: string = /*wgsl*/ ` fn pow2( x : f32 ) -> f32 { diff --git a/src/assets/shader/post/FXAAShader.ts b/src/assets/shader/post/FXAAShader.ts index d867281a..903a2fa9 100644 --- a/src/assets/shader/post/FXAAShader.ts +++ b/src/assets/shader/post/FXAAShader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let FXAAShader: string = /*wgsl*/ ` #include 'BitUtil' struct FragmentOutput { diff --git a/src/assets/shader/post/GlobalFog_shader.ts b/src/assets/shader/post/GlobalFog_shader.ts index 23ba63c3..169c60e1 100644 --- a/src/assets/shader/post/GlobalFog_shader.ts +++ b/src/assets/shader/post/GlobalFog_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let GlobalFog_shader = /* wgsl */ ` var PI: f32 = 3.14159265359; #include "GlobalUniform" diff --git a/src/assets/shader/quad/Quad_shader.ts b/src/assets/shader/quad/Quad_shader.ts index a3225e55..3f8a96b6 100644 --- a/src/assets/shader/quad/Quad_shader.ts +++ b/src/assets/shader/quad/Quad_shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let FullQuad_vert_wgsl: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "GlobalUniform" @@ -31,7 +34,9 @@ export let FullQuad_vert_wgsl: string = /*wgsl*/ ` return output ; } ` - +/** + * @internal + */ export let Quad_vert_wgsl: string = /*wgsl*/ ` #include "WorldMatrixUniform" #include "GlobalUniform" @@ -59,7 +64,9 @@ export let Quad_vert_wgsl: string = /*wgsl*/ ` return VertexOutput(TEXCOORD_1, vec4(uv, 0.0, 1.0)); } ` - +/** + * @internal + */ export let Quad_frag_wgsl: string = /*wgsl*/ ` struct FragmentOutput { @location(auto) o_Target: vec4 @@ -81,7 +88,9 @@ export let Quad_frag_wgsl: string = /*wgsl*/ ` return FragmentOutput(color); } ` - +/** + * @internal + */ export let Quad_depth2d_frag_wgsl: string = /*wgsl*/ ` struct FragmentOutput { @location(auto) o_Target: vec4 @@ -108,7 +117,9 @@ export let Quad_depth2d_frag_wgsl: string = /*wgsl*/ ` return FragmentOutput(vec4(depth,0.0,0.0,1.0)); } ` - +/** + * @internal + */ export let Quad_depthCube_frag_wgsl: string = /*wgsl*/ ` struct FragmentOutput { @location(auto) o_Target: vec4 @@ -172,6 +183,9 @@ export let Quad_depthCube_frag_wgsl: string = /*wgsl*/ ` } ` +/** + * @internal + */ export let Quad_depth2dArray_frag_wgsl: string = /*wgsl*/ ` struct FragmentOutput { @location(auto) o_Target: vec4 diff --git a/src/assets/shader/sky/CubeSky_Shader.ts b/src/assets/shader/sky/CubeSky_Shader.ts index 90f40b06..8405c6fc 100644 --- a/src/assets/shader/sky/CubeSky_Shader.ts +++ b/src/assets/shader/sky/CubeSky_Shader.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export class CubeSky_Shader { public static sky_vs_frag_wgsl: string = /* wgsl */ ` #include "WorldMatrixUniform" diff --git a/src/assets/shader/utils/BitUtil.ts b/src/assets/shader/utils/BitUtil.ts index eaa16959..7bd38868 100644 --- a/src/assets/shader/utils/BitUtil.ts +++ b/src/assets/shader/utils/BitUtil.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let BitUtil = /* wgsl */` const inv256:f32 = 1.0/256.0; diff --git a/src/assets/shader/utils/ColorUtil.ts b/src/assets/shader/utils/ColorUtil.ts index 35b003a8..8de139e2 100644 --- a/src/assets/shader/utils/ColorUtil.ts +++ b/src/assets/shader/utils/ColorUtil.ts @@ -1,3 +1,6 @@ +/** + * @internal + */ export let ColorUtil: string = /*wgsl*/ ` fn getHDRColor(color: vec3, exposure: f32) -> vec3 < f32 > { return color * pow(2.4, exposure) ; diff --git a/src/assets/shader/utils/GenerayRandomDir.ts b/src/assets/shader/utils/GenerayRandomDir.ts index 6e1c9971..a8f21f46 100644 --- a/src/assets/shader/utils/GenerayRandomDir.ts +++ b/src/assets/shader/utils/GenerayRandomDir.ts @@ -1,5 +1,6 @@ - - +/** + * @internal + */ export let GenerayRandomDir: string = /*wgsl*/ ` fn madfrac(A:f32, B:f32)-> f32 { return A*B-floor(A*B) ; diff --git a/src/index.ts b/src/index.ts index 18fa8bea..0f923bf7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -465,7 +465,6 @@ export * from "./materials/LambertMaterial" export * from "./materials/LitMaterial" export * from "./materials/Material" export * from "./materials/MaterialRegister" -export * from "./materials/PhysicMaterial" export * from "./materials/ReflectionMaterial" export * from "./materials/SkyMaterial" export * from "./materials/UnLitMaterial" diff --git a/src/loader/parser/gltf/GLTFSubParserConverter.ts b/src/loader/parser/gltf/GLTFSubParserConverter.ts index e316fe85..0e3f26f0 100644 --- a/src/loader/parser/gltf/GLTFSubParserConverter.ts +++ b/src/loader/parser/gltf/GLTFSubParserConverter.ts @@ -1,16 +1,13 @@ import { AnimatorComponent, BlendShapeData, BlendShapePropertyData, GLTFMaterial, LitMaterial, Material, Matrix4, PropertyAnimationClip, SkinnedMeshRenderer2 } from "../../.."; import { Engine3D } from "../../../Engine3D"; -import { SkeletonAnimationComponent } from "../../../components/SkeletonAnimationComponent"; import { DirectLight } from "../../../components/lights/DirectLight"; import { PointLight } from "../../../components/lights/PointLight"; import { SpotLight } from "../../../components/lights/SpotLight"; import { MeshRenderer } from "../../../components/renderer/MeshRenderer"; -import { SkinnedMeshRenderer } from "../../../components/renderer/SkinnedMeshRenderer"; import { Object3D } from "../../../core/entities/Object3D"; import { GeometryBase } from "../../../core/geometry/GeometryBase"; import { VertexAttributeName } from "../../../core/geometry/VertexAttributeName"; import { BlendMode } from "../../../materials/BlendMode"; -import { PhysicMaterial } from "../../../materials/PhysicMaterial"; import { Color } from "../../../math/Color"; import { RADIANS_TO_DEGREES } from "../../../math/MathUtil"; import { Quaternion } from "../../../math/Quaternion"; diff --git a/src/loader/parser/gltf/extends/KHR_materials_clearcoat.ts b/src/loader/parser/gltf/extends/KHR_materials_clearcoat.ts index e8e5aa01..2c99e327 100644 --- a/src/loader/parser/gltf/extends/KHR_materials_clearcoat.ts +++ b/src/loader/parser/gltf/extends/KHR_materials_clearcoat.ts @@ -1,7 +1,7 @@ // https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_clearcoat -import { Material } from '../../../..'; -import { PhysicMaterial } from '../../../../materials/PhysicMaterial'; +import { LitMaterial } from '../../../../materials/LitMaterial'; +import { Material } from '../../../../materials/Material'; /** * @internal @@ -16,12 +16,12 @@ export class KHR_materials_clearcoat { let KHR_materials_clearcoat = extensions[`KHR_materials_clearcoat`]; if (`clearcoatFactor` in KHR_materials_clearcoat) { dmaterial.clearcoatFactor = KHR_materials_clearcoat[`clearcoatFactor`]; - (tMaterial as PhysicMaterial).clearcoatFactor = dmaterial.clearcoatFactor; + (tMaterial as LitMaterial).clearcoatFactor = dmaterial.clearcoatFactor; } if (`clearcoatRoughnessFactor` in KHR_materials_clearcoat) { dmaterial.clearcoatRoughnessFactor = KHR_materials_clearcoat[`clearcoatRoughnessFactor`]; - (tMaterial as PhysicMaterial).clearcoatRoughnessFactor = dmaterial.clearcoatRoughnessFactor; + (tMaterial as LitMaterial).clearcoatRoughnessFactor = dmaterial.clearcoatRoughnessFactor; } } } diff --git a/src/loader/parser/prefab/mats/shader/StandShader.ts b/src/loader/parser/prefab/mats/shader/StandShader.ts index ae3fc388..f4ec1ff0 100644 --- a/src/loader/parser/prefab/mats/shader/StandShader.ts +++ b/src/loader/parser/prefab/mats/shader/StandShader.ts @@ -3,7 +3,6 @@ import { Texture } from "../../../../../gfx/graphics/webGpu/core/texture/Texture import { RenderShaderPass } from "../../../../../gfx/graphics/webGpu/shader/RenderShaderPass"; import { Color } from "../../../../../math/Color"; import { Vector4 } from "../../../../../math/Vector4"; -import { RegisterShader } from "../../../../../util/SerializeDecoration"; import { Shader } from "../../../../../gfx/graphics/webGpu/shader/Shader"; diff --git a/src/materials/LitMaterial.ts b/src/materials/LitMaterial.ts index 227e1ee3..30c91d65 100644 --- a/src/materials/LitMaterial.ts +++ b/src/materials/LitMaterial.ts @@ -1,11 +1,7 @@ -import { Vector4 } from ".."; -import { Engine3D } from "../Engine3D"; import { Texture } from "../gfx/graphics/webGpu/core/texture/Texture"; -import { RenderShaderPass } from "../gfx/graphics/webGpu/shader/RenderShaderPass"; import { StandShader } from "../loader/parser/prefab/mats/shader/StandShader"; import { Color } from "../math/Color"; import { Material } from "./Material"; -import { PhysicMaterial } from "./PhysicMaterial"; export class LitMaterial extends Material { diff --git a/src/materials/MaterialRegister.ts b/src/materials/MaterialRegister.ts index f8303a37..4f666726 100644 --- a/src/materials/MaterialRegister.ts +++ b/src/materials/MaterialRegister.ts @@ -11,7 +11,6 @@ export type MaterialClassName = | 'GUIMaterial' | 'ChromaKeyMaterial' | 'LambertMaterial' - | 'PhysicMaterial' | 'SkyMaterial' | 'UnLitMaterial' | 'VideoMaterial' diff --git a/src/materials/PhysicMaterial.ts b/src/materials/PhysicMaterial.ts deleted file mode 100644 index 35e9028e..00000000 --- a/src/materials/PhysicMaterial.ts +++ /dev/null @@ -1,516 +0,0 @@ -import { Engine3D } from '../Engine3D'; -import { Texture } from '../gfx/graphics/webGpu/core/texture/Texture'; -import { Color } from '../math/Color'; -import { Vector4 } from '../math/Vector4'; -import { Material } from './Material'; - -/** - * @internal - * @group Material - */ -export class PhysicMaterial extends Material { - - constructor() { - super(); - this.init(); - } - - private init() { - let bdrflutTex = Engine3D.res.getTexture(`BRDFLUT`); - this.brdfLUT = bdrflutTex; - - this.setDefault(); - this.baseMap = Engine3D.res.whiteTexture; - this.normalMap = Engine3D.res.normalTexture; - this.emissiveMap = Engine3D.res.blackTexture; - this.alphaCutoff = 0.5; - } - - // public get shader(): RenderShader { - // return this._shader; - // } - - /** - * Set the render shader default value - */ - public setDefault() { - let colorPass = this.shader.getDefaultColorShader(); - colorPass.setUniformFloat(`shadowBias`, 0.00035); - colorPass.setUniformColor(`baseColor`, new Color()); - colorPass.setUniformColor(`emissiveColor`, new Color(1, 1, 1)); - colorPass.setUniformVector4(`materialF0`, new Vector4(0.04, 0.04, 0.04, 1)); - colorPass.setUniformColor(`specularColor`, new Color(0.04, 0.04, 0.04)); - colorPass.setUniformFloat(`envIntensity`, 1); - colorPass.setUniformFloat(`normalScale`, 1); - colorPass.setUniformFloat(`roughness`, 1.0); - colorPass.setUniformFloat(`metallic`, 0.0); - colorPass.setUniformFloat(`ao`, 1.0); - colorPass.setUniformFloat(`roughness_min`, 0.0); - colorPass.setUniformFloat(`roughness_max`, 1.0); - colorPass.setUniformFloat(`metallic_min`, 0.0); - colorPass.setUniformFloat(`metallic_max`, 1.0); - colorPass.setUniformFloat(`emissiveIntensity`, 0.0); - colorPass.setUniformFloat(`alphaCutoff`, 0.0); - colorPass.setUniformFloat(`ior`, 1.5); - colorPass.setUniformFloat(`empty`, 1.5); - colorPass.setUniformFloat(`clearcoatFactor`, 0.0); - colorPass.setUniformFloat(`clearcoatRoughnessFactor`, 0.0); - colorPass.setUniformColor(`clearcoatColor`, new Color(1, 1, 1)); - colorPass.setUniformFloat(`clearcoatWeight`, 0.0); - colorPass.setUniformFloat(`empty`, 0.0); - - colorPass.setUniformVector4(`baseMapOffsetSize`, new Vector4(0, 0, 1, 1)); - colorPass.setUniformVector4(`normalMapOffsetSize`, new Vector4(0, 0, 1, 1)); - colorPass.setUniformVector4(`emissiveMapOffsetSize`, new Vector4(0, 0, 1, 1)); - colorPass.setUniformVector4(`roughnessMapOffsetSize`, new Vector4(0, 0, 1, 1)); - colorPass.setUniformVector4(`metallicMapOffsetSize`, new Vector4(0, 0, 1, 1)); - colorPass.setUniformVector4(`aoMapOffsetSize`, new Vector4(0, 0, 1, 1)); - } - - - public get baseMap(): Texture { - return this.shader.getDefaultColorShader().getTexture(`baseMap`); - } - - public set baseMap(value: Texture) { - this.shader.getDefaultColorShader().setTexture(`baseMap`, value); - } - - public get baseColor(): Color { - return this.shader.getDefaultColorShader().getUniform(`baseColor`); - } - - public set baseColor(value: Color) { - this.shader.getDefaultColorShader().setUniformColor(`baseColor`, value); - } - - public get normalMap(): Texture { - return this.shader.getDefaultColorShader().getTexture(`normalMap`); - } - - public set normalMap(value: Texture) { - this.shader.getDefaultColorShader().setTexture(`normalMap`, value); - } - - public get doubleSide(): boolean { - return this.shader.getDefaultColorShader().doubleSide; - } - public set doubleSide(value: boolean) { - this.shader.getDefaultColorShader().doubleSide = value; - } - - public get alphaCutoff(): any { - return this.shader.getDefaultColorShader().shaderState.alphaCutoff; - } - public set alphaCutoff(value: any) { - this.shader.getDefaultColorShader().setDefine("USE_ALPHACUT", true); - this.shader.getDefaultColorShader().shaderState.alphaCutoff = value; - this.shader.getDefaultColorShader().setUniform(`alphaCutoff`, value); - } - - public get emissiveColor(): Color { - return this.shader.getDefaultColorShader().getUniform(`emissiveColor`); - } - - public set emissiveColor(value: Color) { - this.shader.getDefaultColorShader().setUniform(`emissiveColor`, value); - } - - public get emissiveIntensity(): number { - return this.shader.getDefaultColorShader().getUniform(`emissiveIntensity`); - } - - public set emissiveIntensity(value: number) { - this.shader.getDefaultColorShader().setUniform(`emissiveIntensity`, value); - } - - /** - * get transformUV1 - */ - public get baseMapUVOffsetSize(): Vector4 { - return this.shader.getDefaultColorShader().uniforms[`baseMapOffsetSize`].vector4; - } - - /** - * set transformUV1 - */ - public set baseMapUVOffsetSize(value: Vector4) { - this.shader.getDefaultColorShader().setUniform(`baseMapOffsetSize`, value); - } - - /** - * get transformUV1 - */ - public get normalMapUVOffsetSize(): Vector4 { - return this.shader.getDefaultColorShader().uniforms[`normalMapOffsetSize`].vector4; - } - - /** - * set transformUV1 - */ - public set normalMapUVOffsetSize(value: Vector4) { - this.shader.getDefaultColorShader().setUniform(`normalMapOffsetSize`, value); - } - - /** - * get transformUV1 - */ - public get emissiveMapUVOffsetSize(): Vector4 { - return this.shader.getDefaultColorShader().uniforms[`emissiveMapOffsetSize`].vector4; - } - - /** - * set transformUV1 - */ - public set emissiveMapUVOffsetSize(value: Vector4) { - this.shader.getDefaultColorShader().setUniform(`emissiveMapOffsetSize`, value); - } - - /** - * get transformUV1 - */ - public get pbrMetallicMapUVOffsetSize(): Vector4 { - return this.shader.getDefaultColorShader().uniforms[`roughnessMapOffsetSize`].vector4; - } - - /** - * set transformUV1 - */ - public set pbrMetallicMapUVOffsetSize(value: Vector4) { - this.shader.getDefaultColorShader().setUniform(`roughnessMapOffsetSize`, value); - } - - public get depthWriteEnabled(): boolean { - return this.shader.getDefaultColorShader().shaderState.depthWriteEnabled; - } - public set depthWriteEnabled(value: boolean) { - this.shader.getDefaultColorShader().shaderState.depthWriteEnabled = value; - } - - /** - * get reflectivity - */ - public get materialF0(): Vector4 { - return this.shader.getDefaultColorShader().uniforms[`materialF0`].vector4; - } - - /** - * set reflectivity - */ - public set materialF0(value: Vector4) { - this.shader.getDefaultColorShader().setUniform(`materialF0`, value); - } - - /** - * get specularColor - */ - public get specularColor(): Color { - return this.shader.getDefaultColorShader().uniforms[`specularColor`].color; - } - - /**specularColor - * set reflectivity - */ - public set specularColor(value: Color) { - this.shader.getDefaultColorShader().setUniform(`specularColor`, value); - } - - /** - * get roughness - */ - public get roughness(): number { - return this.shader.getDefaultColorShader().uniforms[`roughness`].value; - } - - /** - * set roughness - */ - public set roughness(value: number) { - this.shader.getDefaultColorShader().setUniform(`roughness`, value); - } - - /** - * get metallic - */ - public get metallic(): number { - return this.shader.getDefaultColorShader().uniforms[`metallic`].value; - } - - /** - * set metallic - */ - public set metallic(value: number) { - this.shader.getDefaultColorShader().setUniform(`metallic`, value); - } - - /** - * get Ambient Occlussion, dealing with the effect of ambient light on object occlusion - */ - public get ao(): number { - return this.shader.getDefaultColorShader().uniforms[`ao`].value; - } - - /** - * set Ambient Occlussion, dealing with the effect of ambient light on object occlusion - */ - public set ao(value: number) { - this.shader.getDefaultColorShader().setUniform(`ao`, value); - } - - /** - * get min metallic - */ - public get metallic_min(): number { - return this.shader.getDefaultColorShader().uniforms[`metallic_min`].value; - } - - /** - * set min metallic - */ - public set metallic_min(value: number) { - this.shader.getDefaultColorShader().setUniform(`metallic_min`, value); - } - - /** - * get max metallic - */ - public get metallic_max(): number { - return this.shader.getDefaultColorShader().uniforms[`metallic_max`].value; - } - - /** - * set max metallic - */ - public set metallic_max(value: number) { - this.shader.getDefaultColorShader().setUniform(`metallic_max`, value); - } - - /** - * get min roughness - */ - public get roughness_min(): number { - return this.shader.getDefaultColorShader().uniforms[`roughness_min`].value; - } - - /** - * set min roughness - */ - public set roughness_min(value: number) { - this.shader.getDefaultColorShader().setUniform(`roughness_min`, value); - } - - /** - * get max roughness - */ - public get roughness_max(): number { - return this.shader.getDefaultColorShader().uniforms[`roughness_max`].value; - } - - /** - * set max roughness - */ - public set roughness_max(value: number) { - this.shader.getDefaultColorShader().setUniform(`roughness_max`, value); - } - - /** - * Get the influence of Normal mapping on materials - */ - public get normalScale(): number { - return this.shader.getDefaultColorShader().uniforms[`normalScale`].value; - } - - /** - * Set the influence of Normal mapping on materials - */ - public set normalScale(value: number) { - this.shader.getDefaultColorShader().setUniform(`normalScale`, value); - } - - /** - * get Mask Map - * R_chanel -> AoMap - * G_chanel -> Roughness - * B_chanel -> Metallic - * A_chanel -> C - */ - public get maskMap(): Texture { - return this.shader.getDefaultColorShader().textures[`maskMap`]; - } - - /** - * set Mask Map - * R_chanel -> AoMap - * G_chanel -> Roughness - * B_chanel -> Metallic - * A_chanel -> C - */ - public set maskMap(value: Texture) { - // USE_MR - // USE_ORMC - // USE_RMOC - // USE_CRMC - this.shader.getDefaultColorShader().setDefine(`USE_MR`, true); - this.shader.getDefaultColorShader().setTexture(`maskMap`, value); - } - - /** - * set Ambient Occlussion Map, dealing with the effect of ambient light on object occlusion - */ - public set aoMap(value: Texture) { - if (!value) return; - this.shader.getDefaultColorShader().setTexture(`aoMap`, value); - if (value != Engine3D.res.whiteTexture) { - this.shader.getDefaultColorShader().setDefine(`USE_AOTEX`, true); - } - } - - /** - * get Ambient Occlussion Map, dealing with the effect of ambient light on object occlusion - */ - public get aoMap(): Texture { - return this.shader.getDefaultColorShader().textures[`aoMap`]; - } - - /** - * set clearCoatRoughnessMap - */ - public set clearCoatRoughnessMap(value: Texture) { - if (!value) return; - console.log("USE_CLEARCOAT_ROUGHNESS"); - - this.shader.getDefaultColorShader().setTexture(`clearCoatRoughnessMap`, value); - this.shader.getDefaultColorShader().setDefine(`USE_CLEARCOAT_ROUGHNESS`, true); - } - - /** - * get clearCoatRoughnessMap - */ - public get clearCoatRoughnessMap(): Texture { - return this.shader.getDefaultColorShader().textures[`clearCoatRoughnessMap`]; - } - - /** - * get brdf query map - */ - public get brdfLUT(): Texture { - return this.shader.getDefaultColorShader().textures[`brdfLUT`]; - } - - /** - * set brdf query map - */ - public set brdfLUT(value: Texture) { - this.shader.getDefaultColorShader().setTexture(`brdfLUT`, value); - this.shader.getDefaultColorShader().setTexture(`brdflutMap`, value); - } - - /** - * get emissive map - */ - public get emissiveMap(): Texture { - return this.shader.getDefaultColorShader().textures[`emissiveMap`]; - } - - /** - * set emissive map - */ - public set emissiveMap(value: Texture) { - this.shader.getDefaultColorShader().setTexture(`emissiveMap`, value); - } - - /** - * set intensity of environment light or color of sampled by texture - */ - public set envIntensity(value: number) { - this.shader.getDefaultColorShader().setUniformFloat(`envIntensity`, value); - } - - /** - * get intensity of environment light or color of sampled by texture - */ - public get envIntensity() { - return this.shader.getDefaultColorShader().uniforms[`envIntensity`].value; - } - - /** - * set factor of refractive - */ - public set ior(value: number) { - this.shader.getDefaultColorShader().setUniformFloat(`ior`, value); - } - - /** - * get factor of refractive - */ - public get ior(): number { - return this.shader.getDefaultColorShader().uniforms[`ior`].value; - } - - /** - * valid USE_CLEARCOAT define in shader - */ - public useCleanCoat() { - this.shader.getDefaultColorShader().setDefine("USE_CLEARCOAT", true); - } - - /** - * Set the factor of the clearcoat - */ - public set clearcoatFactor(value: number) { - this.shader.getDefaultColorShader().setUniformFloat(`clearcoatFactor`, value); - this.useCleanCoat(); - } - - /** - * get the factor of the clearcoat - */ - public get clearcoatFactor(): number { - return this.shader.getDefaultColorShader().uniforms[`clearcoatFactor`].value; - } - - /** - * set the factor of the clearcoat Roughness - */ - public set clearcoatRoughnessFactor(value: number) { - this.shader.getDefaultColorShader().setUniformFloat(`clearcoatRoughnessFactor`, value); - this.useCleanCoat(); - } - - /** - * get the factor of the clearcoat Roughness - */ - public get clearcoatRoughnessFactor(): number { - return this.shader.getDefaultColorShader().uniforms[`clearcoatRoughnessFactor`].value; - } - - /** - * set the weight of the clearcoat - */ - public set clearcoatWeight(value: number) { - this.shader.getDefaultColorShader().setUniformFloat(`clearcoatWeight`, value); - this.useCleanCoat(); - } - - /** - * get the weight of the clearcoat - */ - public get clearcoatWeight(): number { - return this.shader.getDefaultColorShader().uniforms[`clearcoatWeight`].value; - } - - /** - * get the color of the clearcoat - */ - public set clearcoatColor(value: Color) { - this.shader.getDefaultColorShader().setUniformColor(`clearcoatColor`, value); - this.useCleanCoat(); - } - - /** - * set the color of the clearcoat - */ - public get clearcoatColor(): Color { - return this.shader.getDefaultColorShader().uniforms[`clearcoatColor`].color; - } -} From a939ce62ccbe3e6db6e964ebcf2921d975b23a1c Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Mon, 29 Jul 2024 13:04:06 +0800 Subject: [PATCH 13/25] feat: add GridObject (#436) --- package.json | 2 +- samples/base/Sample_AddRemove.ts | 6 +- samples/base/Sample_BoundingBox.ts | 18 +-- ...le_UseComponent.ts => Sample_Component.ts} | 17 ++- samples/base/Sample_ComponentLife.ts | 65 ---------- samples/base/Sample_ComponentLifeCycle.ts | 49 -------- samples/base/Sample_MatrixAllocation.ts | 8 +- samples/base/Sample_Transform.ts | 11 +- samples/base/_Sample_InitEngine.ts | 117 ------------------ ...tureSample.ts => Sample_TextureSampler.ts} | 8 +- samples/sky/Sample_SolidColorSky.ts | 5 +- src/index.ts | 1 + src/materials/Material.ts | 8 ++ src/materials/UnLitMaterial.ts | 5 +- src/util/AxisObject.ts | 2 +- src/util/BoundUtil.ts | 4 + src/util/GridObject.ts | 102 +++++++++++++++ .../transformUtil/Object3DTransformTools.ts | 5 - 18 files changed, 160 insertions(+), 273 deletions(-) rename samples/base/{Sample_UseComponent.ts => Sample_Component.ts} (85%) delete mode 100644 samples/base/Sample_ComponentLife.ts delete mode 100644 samples/base/Sample_ComponentLifeCycle.ts delete mode 100644 samples/base/_Sample_InitEngine.ts rename samples/render/{Sample_TextureSample.ts => Sample_TextureSampler.ts} (96%) create mode 100644 src/util/GridObject.ts diff --git a/package.json b/package.json index 99a1ccea..89a4a284 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/core", - "version": "0.8.3-dev.1", + "version": "0.8.3-dev.2", "author": "Orillusion", "description": "Orillusion WebGPU Engine", "type": "module", diff --git a/samples/base/Sample_AddRemove.ts b/samples/base/Sample_AddRemove.ts index 9871129a..a0ee7456 100644 --- a/samples/base/Sample_AddRemove.ts +++ b/samples/base/Sample_AddRemove.ts @@ -1,4 +1,4 @@ -import { Engine3D, Scene3D, CameraUtil, View3D, AtmosphericComponent, ComponentBase, Time, AxisObject, Object3DUtil, KelvinUtil, DirectLight, Object3D, HoverCameraController, MeshRenderer, LitMaterial, BoxGeometry, UnLit, UnLitMaterial, Interpolator, FXAAPost, PostProcessingComponent } from "@orillusion/core"; +import { Engine3D, Scene3D, CameraUtil, View3D, AtmosphericComponent, ComponentBase, Time, AxisObject, Object3DUtil, KelvinUtil, DirectLight, Object3D, HoverCameraController, MeshRenderer, LitMaterial, BoxGeometry, UnLit, UnLitMaterial, Interpolator, FXAAPost, PostProcessingComponent, GridObject } from "@orillusion/core"; import { GUIHelp } from "@orillusion/debug/GUIHelp"; // sample use component @@ -43,6 +43,10 @@ class Sample_AddRemove { scene.addChild(lightObj); sky.relativeTransform = dirLight.transform; + // add a grid + let grid = new GridObject(10000, 100); + scene.addChild(grid) + // create a view with target scene and camera this.view = new View3D(); this.view.scene = scene; diff --git a/samples/base/Sample_BoundingBox.ts b/samples/base/Sample_BoundingBox.ts index d0b83f5b..bb7b74f9 100644 --- a/samples/base/Sample_BoundingBox.ts +++ b/samples/base/Sample_BoundingBox.ts @@ -1,5 +1,5 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; -import { Color, Engine3D, Object3D, Object3DUtil, Transform, View3D, } from '@orillusion/core'; +import { Color, Engine3D, GridObject, Object3D, Object3DUtil, Transform, View3D, } from '@orillusion/core'; import { GUIUtil } from '@samples/utils/GUIUtil'; import { createExampleScene, createSceneParam } from '@samples/utils/ExampleScene'; import { Graphic3D } from '@orillusion/graphic'; @@ -27,21 +27,22 @@ class Sample_BoundingBox { this.box = box; this.view = exampleScene.view; - // add a graphic3D to draw lines - this.graphic3D = new Graphic3D(); - exampleScene.scene.addChild(this.graphic3D); - + let parent = this.container = new Object3D(); parent.addChild(box); exampleScene.scene.addChild(parent); - GUIHelp.open(); - GUIHelp.addButton('Remove Box', () => { box.transform.parent && box.removeFromParent(); }) - GUIHelp.addButton('Add Box', () => { !box.transform.parent && parent.addChild(box); }) + let grid = new GridObject(1000, 100); + exampleScene.scene.addChild(grid); + GUIHelp.open(); GUIUtil.renderTransform(parent.transform, true, 'Container'); GUIUtil.renderTransform(box.transform, true, 'Box'); + // add a graphic3D to draw lines + this.graphic3D = new Graphic3D(); + exampleScene.scene.addChild(this.graphic3D); + } logChange() { @@ -52,7 +53,6 @@ class Sample_BoundingBox { green = new Color(0, 1, 0, 1); loop() { this.graphic3D.drawBoundingBox(this.box.instanceID, this.box.bound as any, this.green); - this.graphic3D.drawBoundingBox(this.container.instanceID, this.container.bound as any, this.red); } } diff --git a/samples/base/Sample_UseComponent.ts b/samples/base/Sample_Component.ts similarity index 85% rename from samples/base/Sample_UseComponent.ts rename to samples/base/Sample_Component.ts index ba1f0cda..42365c30 100644 --- a/samples/base/Sample_UseComponent.ts +++ b/samples/base/Sample_Component.ts @@ -33,7 +33,6 @@ class Sample_UseComponent { // register a component let component = cube.addComponent(RotateComponent); - // gui GUIHelp.init(); GUIHelp.add(component, 'enable'); @@ -52,33 +51,33 @@ class Sample_UseComponent { directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); directLight.castShadow = true; directLight.intensity = 3; - GUIUtil.renderDirLight(directLight); scene.addChild(lightObj3D); } } } - +/* + * simple component of lifecyle + */ class RotateComponent extends ComponentBase { public init(param?: any): void { - console.log('RotateComponent init, name : ', this.object3D.name); + console.log('RotateComponent init'); } public start(): void { - console.log('RotateComponent start, name :', this.object3D.name); + console.log('RotateComponent start'); } public onUpdate(): void { + console.log('RotateComponent update') this.transform.rotationY = Math.sin(Time.time * 0.01) * 90; } public onEnable(view?: View3D) { - console.log('RotateComponent init, name : ', this.object3D.name); - this._enable = true; + console.log('RotateComponent enable'); } public onDisable(view?: View3D) { - console.log('RotateComponent init, name : ', this.object3D.name); - this._enable = false; + console.log('RotateComponent disable'); } } diff --git a/samples/base/Sample_ComponentLife.ts b/samples/base/Sample_ComponentLife.ts deleted file mode 100644 index b34f0d5d..00000000 --- a/samples/base/Sample_ComponentLife.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Engine3D, Scene3D, CameraUtil, View3D, AtmosphericComponent, ComponentBase, Time, AxisObject, Object3DUtil, KelvinUtil, DirectLight, Object3D, HoverCameraController } from "@orillusion/core"; - -// sample use component -class Sample_ComponentLife { - async run() { - // init engine - await Engine3D.init(); - // create new Scene - let scene = new Scene3D(); - // add atmospheric sky - scene.addComponent(AtmosphericComponent); - - // init camera3D - let mainCamera = CameraUtil.createCamera3D(null, scene); - mainCamera.perspective(60, Engine3D.aspect, 1, 2000.0); - let hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController); - hoverCameraController.setCamera(15, -15, 10); - - // create a view with target scene and camera - let view = new View3D(); - view.scene = scene; - view.camera = mainCamera; - - // start render - Engine3D.startRenderView(view); - - // create cube - let cube = Object3DUtil.GetSingleCube(2, 4, 1, 0.7, 1, 0.5); - cube.name = 'cube' - scene.addChild(cube); - - // register a component - let component = cube.addComponent(LifeComponent); - } -} - - -class LifeComponent extends ComponentBase { - public init(param?: any): void { - console.log('init'); - - } - public start(): void { - console.log('start'); - } - - public onBeforeUpdate(view?: View3D) { - console.log('onBeforeUpdate'); - } - - public onCompute(view?: View3D, command?: GPUCommandEncoder) { - console.log('onUpdate'); - } - - public onUpdate(): void { - console.log('onUpdate'); - } - - public onLateUpdate(view?: View3D) { - console.log('onLateUpdate'); - } -} - - -new Sample_ComponentLife().run(); \ No newline at end of file diff --git a/samples/base/Sample_ComponentLifeCycle.ts b/samples/base/Sample_ComponentLifeCycle.ts deleted file mode 100644 index dc4b4d15..00000000 --- a/samples/base/Sample_ComponentLifeCycle.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Engine3D, ComponentBase, Object3DUtil } from "@orillusion/core"; -import { createExampleScene } from "@samples/utils/ExampleScene"; - -class TestComponent1 extends ComponentBase { - i = 0; - start(): void { - console.log('TestComponent1 start'); - } - onUpdate() { - this.i += 1; - console.log("TestComponent1 onUpdate"); - } - onLateUpdate() { - console.log("TestComponent1 onLateUpdate", this.i); - } -} - -class TestComponent2 extends ComponentBase { - i = 0; - start() { - console.log("TestComponent2 start"); - this.transform.rotationY = 0.0; - this.object3D.addComponent(TestComponent1); - } - onUpdate() { - this.i += 1; - this.transform.rotationY += 1.0; - console.log("TestComponent2 onUpdate"); - } - onLateUpdate() { - console.log("TestComponent2 onLateUpdate", this.i); - } -} - -Engine3D.setting.shadow.type = 'HARD' -Engine3D.setting.shadow.shadowBound = 100 -Engine3D.init().then(() => { - let exampleScene = createExampleScene(); - //floor - const floor = Object3DUtil.GetSingleCube(100, 1, 100, 0.5, 0.5, 0.5); - floor.y = -5; - exampleScene.scene.addChild(floor); - // 新建对象 - const obj = Object3DUtil.GetSingleCube(10, 10, 10, 1, 1, 1) - obj.addComponent(TestComponent2); - exampleScene.scene.addChild(obj); - // 开始渲染 - Engine3D.startRenderView(exampleScene.view); -}); \ No newline at end of file diff --git a/samples/base/Sample_MatrixAllocation.ts b/samples/base/Sample_MatrixAllocation.ts index d5c0121a..ae9be4e2 100644 --- a/samples/base/Sample_MatrixAllocation.ts +++ b/samples/base/Sample_MatrixAllocation.ts @@ -1,6 +1,6 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; import { Stats } from '@orillusion/stats' -import { Engine3D, Scene3D, AtmosphericComponent, CameraUtil, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, KelvinUtil, View3D, Matrix4 } from '@orillusion/core'; +import { Engine3D, Scene3D, AtmosphericComponent, CameraUtil, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, KelvinUtil, View3D, Matrix4, GridObject } from '@orillusion/core'; import { GUIUtil } from '@samples/utils/GUIUtil'; class Sample_MatrixAllocation { @@ -29,6 +29,8 @@ class Sample_MatrixAllocation { mr.material = mat; scene.addChild(cubeObj); + scene.addChild(new GridObject(1000, 100)) + let view = new View3D(); view.scene = scene; view.camera = mainCamera; @@ -38,8 +40,8 @@ class Sample_MatrixAllocation { GUIHelp.init(); GUIHelp.addButton('add', () => { let obj = new Object3D(); - obj.x = -5 + Math.random() * 10; - obj.z = -5 + Math.random() * 10; + obj.x = -10 + Math.random() * 20; + obj.z = -10 + Math.random() * 20; let mr = obj.addComponent(MeshRenderer); mr.geometry = new BoxGeometry(); mr.material = new LitMaterial(); diff --git a/samples/base/Sample_Transform.ts b/samples/base/Sample_Transform.ts index 9c16aa87..3308aae3 100644 --- a/samples/base/Sample_Transform.ts +++ b/samples/base/Sample_Transform.ts @@ -1,4 +1,4 @@ -import { Engine3D, Scene3D, AtmosphericComponent, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, View3D, Camera3D } from "@orillusion/core"; +import { Engine3D, Scene3D, AtmosphericComponent, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, View3D, Camera3D, GridObject } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; import * as dat from "dat.gui" @@ -43,14 +43,19 @@ const box: Object3D = new Object3D(); // add MeshRenderer let mr: MeshRenderer = box.addComponent(MeshRenderer); // set geometry -mr.geometry = new BoxGeometry(5, 5, 5); +mr.geometry = new BoxGeometry(1, 1, 1); // set material mr.material = new LitMaterial(); // set rotation -box.rotationY = 45; +box.rotationY = 0; +box.y = 0.5 // add object scene3D.addChild(box); +// add a grid +let grid = new GridObject(1000, 100); +scene3D.addChild(grid) + // create a view with target scene and camera let view = new View3D(); view.scene = scene3D; diff --git a/samples/base/_Sample_InitEngine.ts b/samples/base/_Sample_InitEngine.ts deleted file mode 100644 index 1c77ec7f..00000000 --- a/samples/base/_Sample_InitEngine.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { AtmosphericComponent, BloomPost, BoxGeometry, CameraUtil, DirectLight, Engine3D, GTAOPost, HoverCameraController, KelvinUtil, LambertMaterial, LitMaterial, MeshRenderer, Object3D, OcclusionSystem, PlaneGeometry, PostProcessingComponent, Quaternion, Scene3D, SphereGeometry, UnLitMaterial, Vector3, Vector3Ex, View3D } from '@orillusion/core'; -import { GUIHelp } from '@orillusion/debug/GUIHelp'; -import { Stats } from '@orillusion/stats'; -import { GUIUtil } from '@samples/utils/GUIUtil'; - -// sample use component -export class Sample_InitEngine { - view: View3D; - async run() { - - OcclusionSystem.enable = false; - Engine3D.setting.shadow.shadowBound = 256 - Engine3D.setting.render.useLogDepth = true - // init engine - await Engine3D.init({ renderLoop: () => this.update() }); - - // create new Scene - let scene = new Scene3D(); - // add atmospheric sky - scene.addComponent(AtmosphericComponent).sunY = 0.6; - scene.addComponent(Stats); - - // init camera3D - let mainCamera = CameraUtil.createCamera3D(null, scene); - mainCamera.perspective(60, Engine3D.aspect, 0.1, 6500000 * 4); - let hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController); - hoverCameraController.setCamera(15, -30, 6500000 * 2); - - // create a view with target scene and camera - this.view = new View3D(); - this.view.scene = scene; - this.view.camera = mainCamera; - - GUIHelp.init(); - - // start render - Engine3D.startRenderView(this.view); - - let postCom = scene.addComponent(PostProcessingComponent); - let post = postCom.addPost(BloomPost) as BloomPost; - let post2 = postCom.addPost(GTAOPost) as GTAOPost; - - GUIUtil.renderBloom(post); - GUIUtil.renderShadowSetting(); - await this.test(); - } - - private async test() { - - /******** light *******/ - { - let lightObj3D = new Object3D(); - lightObj3D.x = 0; - lightObj3D.y = 30; - lightObj3D.z = -40; - lightObj3D.rotationX = 45; - lightObj3D.rotationY = 0; - lightObj3D.rotationZ = 0; - let directLight = lightObj3D.addComponent(DirectLight); - directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); - directLight.castShadow = true; - directLight.intensity = 3; - this.view.scene.addChild(lightObj3D); - - GUIUtil.renderDirLight(directLight, true); - } - { - let sphere = new SphereGeometry(6300000, 120, 120); - let floor = new Object3D(); - let mr = floor.addComponent(MeshRenderer); - mr.geometry = sphere; - mr.material = new LitMaterial(); - this.view.scene.addChild(floor); - } - { - let floorGeo = new PlaneGeometry(100, 100, 10, 10); - let floor = new Object3D(); - let mr = floor.addComponent(MeshRenderer); - mr.geometry = floorGeo; - mr.material = new LitMaterial(); - this.view.scene.addChild(floor); - } - - // let shareGeometry = new BoxGeometry(); - // let mat = new LitMaterial(); - - // let count = 100 * 1000; - // let count = 1 * 1000; - // for (let i = 0; i < count; i++) { - // let box = new Object3D(); - // let mr = box.addComponent(MeshRenderer); - // mr.geometry = shareGeometry; - // mr.material = mat; - // mr.castShadow = true; - // box.scaleX = 2; - // box.scaleY = 2; - // box.scaleZ = 2; - - // box.rotationX = Math.random() * 360; - // box.rotationY = Math.random() * 360; - // box.rotationZ = Math.random() * 360; - - // this.updateList.push(box); - - // box.localPosition = Vector3Ex.sphereXYZ(10, 300, 1, 1, 1); - // this.view.scene.addChild(box); - // } - } - - public updateList: Object3D[] = []; - public update() { - for (let i = 0; i < this.updateList.length; i++) { - const obj = this.updateList[i]; - obj.transform.rotationY += 1; - } - } -} diff --git a/samples/render/Sample_TextureSample.ts b/samples/render/Sample_TextureSampler.ts similarity index 96% rename from samples/render/Sample_TextureSample.ts rename to samples/render/Sample_TextureSampler.ts index 77980f52..5ed4abca 100644 --- a/samples/render/Sample_TextureSample.ts +++ b/samples/render/Sample_TextureSampler.ts @@ -42,7 +42,7 @@ class Sample_TextureSample { let directLight = this.lightObj3D.addComponent(DirectLight); directLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355); directLight.castShadow = true; - directLight.intensity = 6; + directLight.intensity = 3; GUIHelp.init(); GUIUtil.renderDirLight(directLight, false); this.scene.addChild(this.lightObj3D); @@ -63,9 +63,9 @@ class Sample_TextureSample { let component = plane.addComponent(UVMoveComponent); GUIUtil.renderUVMove(component); - // let box = Object3DUtil.GetSingleCube(10, 10, 10, 1, 0.5, 0.5); - // box.y = 5; - // this.scene.addChild(box); + let box = Object3DUtil.GetSingleCube(10, 10, 10, 1, 0.5, 0.5); + box.y = 5; + this.scene.addChild(box); // enum GPUAddressMode let address = {} diff --git a/samples/sky/Sample_SolidColorSky.ts b/samples/sky/Sample_SolidColorSky.ts index b668308b..b2353877 100644 --- a/samples/sky/Sample_SolidColorSky.ts +++ b/samples/sky/Sample_SolidColorSky.ts @@ -1,6 +1,6 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { createExampleScene } from "@samples/utils/ExampleScene"; -import { SolidColorSky, Engine3D, SkyRenderer, Color, Object3DUtil } from "@orillusion/core"; +import { SolidColorSky, Engine3D, SkyRenderer, Color, Object3DUtil, GridObject } from "@orillusion/core"; // sample to display solid color sky class HDRSkyMap { @@ -19,7 +19,8 @@ class HDRSkyMap { GUIHelp.endFolder(); // create a basic cube scene.addChild(Object3DUtil.GetSingleCube(10, 10, 10, 0.6, 0.6, 0.6)); - + // add a grid + scene.addChild(new GridObject(1000, 100)); // start renderer Engine3D.startRenderView(scene.view); } diff --git a/src/index.ts b/src/index.ts index 0f923bf7..475fb716 100644 --- a/src/index.ts +++ b/src/index.ts @@ -581,6 +581,7 @@ export * from "./util/Convert" export * from "./util/DelayUtil" export * from "./util/GeometryUtil" export * from "./util/Global" +export * from "./util/GridObject" export * from "./util/KelvinUtil" export * from "./util/Object3DUtil" export * from "./util/ProfilerUtil" diff --git a/src/materials/Material.ts b/src/materials/Material.ts index b2aa4999..3247f765 100644 --- a/src/materials/Material.ts +++ b/src/materials/Material.ts @@ -137,6 +137,14 @@ export class Material { this._defaultSubShader.setDefine("USE_BILLBOARD", value); } + public get topology(){ + return this._defaultSubShader.topology; + } + + public set topology(value: GPUPrimitiveTopology) { + this._defaultSubShader.topology = value; + } + /** * get render pass by renderType * @param passType diff --git a/src/materials/UnLitMaterial.ts b/src/materials/UnLitMaterial.ts index e22212d9..afff7a91 100644 --- a/src/materials/UnLitMaterial.ts +++ b/src/materials/UnLitMaterial.ts @@ -1,11 +1,8 @@ import { Engine3D } from '../Engine3D'; import { Texture } from '../gfx/graphics/webGpu/core/texture/Texture'; -import { RenderShaderPass } from '../gfx/graphics/webGpu/shader/RenderShaderPass'; import { Color } from '../math/Color'; -import { Vector4 } from '../math/Vector4'; import { Material } from './Material'; -import { PassType } from '../gfx/renderJob/passRenderer/state/PassType'; -import { Shader, UnLitShader } from '..'; +import { UnLitShader } from '..'; /** * Unlit Mateiral diff --git a/src/util/AxisObject.ts b/src/util/AxisObject.ts index cd66b7a0..8b624e42 100644 --- a/src/util/AxisObject.ts +++ b/src/util/AxisObject.ts @@ -4,7 +4,7 @@ import { Vector3 } from '../math/Vector3'; /** - * @internal + * An object group contains xyz axis objects * @group Util */ export class AxisObject extends Object3D { diff --git a/src/util/BoundUtil.ts b/src/util/BoundUtil.ts index e2310b0c..1f624ae5 100644 --- a/src/util/BoundUtil.ts +++ b/src/util/BoundUtil.ts @@ -4,6 +4,10 @@ import { Object3D } from '../core/entities/Object3D'; import { MeshRenderer } from '../components/renderer/MeshRenderer'; import { Matrix4 } from '../math/Matrix4'; +/** + * Utils for calculating bounding boxes + * @group Util + */ export class BoundUtil { private static readonly maxVector = new Vector3(Number.MAX_VALUE * 0.1, Number.MAX_VALUE * 0.1, Number.MAX_VALUE * 0.1); private static readonly minVector = this.maxVector.clone().multiplyScalar(-1); diff --git a/src/util/GridObject.ts b/src/util/GridObject.ts new file mode 100644 index 00000000..43f89f9d --- /dev/null +++ b/src/util/GridObject.ts @@ -0,0 +1,102 @@ +import { UnLitMaterial, Color, MeshRenderer, BlendMode, GeometryBase, Vector3, VertexAttributeName } from ".."; +import { Object3D } from "../core/entities/Object3D"; + +/** + * An object contains grids - two dimensional arrrys of lines + * @group Util + */ +export class GridObject extends Object3D { + public size: number = 100; + + public divisions: number = 10; + + constructor(size: number = 100, divisions: number = 10) { + super(); + this.size = size; + this.divisions = divisions; + this.buildGeometry(); + this.addAxis(); + } + + private buildGeometry() { + const vertices = [] + const indices = [] + const step = this.size / this.divisions; + const halfSize = this.size / 2; + const center = this.divisions / 2; + + for ( let i = 0, k = - halfSize; i <= this.divisions; i ++, k += step ) { + if(i === center ) + continue; + vertices.push( - halfSize, 0, k, halfSize, 0, k ); + vertices.push( k, 0, - halfSize, k, 0, halfSize ); + } + for( let i = 0; i < vertices.length/3; i +=2 ) + indices.push(i, i + 1); + + let grid = new GeometryBase() + grid.setIndices(indices.length > Uint16Array.length ? new Uint32Array(indices) : new Uint16Array(indices)); + grid.setAttribute(VertexAttributeName.position, new Float32Array(vertices)); + grid.addSubGeometry({ + indexStart: 0, + indexCount: indices.length, + vertexStart: 0, + vertexCount: 0, + firstStart: 0, + index: 0, + topology: 0 + }) + + let mat = new UnLitMaterial(); + mat.topology = "line-list"; + mat.baseColor = new Color(1, 1, 1, 0.15); + mat.blendMode = BlendMode.ADD; + mat.castReflection = false; + let mr = this.addComponent(MeshRenderer); + mr.geometry = grid; + mr.material = mat; + } + + private addAxis() { + const halfSize = this.size / 2; + let vertices = new Float32Array([-halfSize,0,0, halfSize,0,0]) + let indexes = new Uint16Array([0, 1, 2, 3]) + + let line = new GeometryBase() + line.setIndices(indexes); + line.setAttribute(VertexAttributeName.position, vertices); + line.addSubGeometry({ + indexStart: 0, + indexCount: indexes.length, + vertexStart: 0, + vertexCount: 0, + firstStart: 0, + index: 0, + topology: 0 + }) + { + let x = new Object3D(); + let mr = x.addComponent(MeshRenderer); + mr.geometry = line; + let mat = mr.material = new UnLitMaterial(); + mat.baseColor = new Color(1, 0, 0, 0.5); + mat.blendMode = BlendMode.ADD; + mat.castReflection = false; + mat.topology = 'line-list'; + this.addChild(x) + } + { + let z = new Object3D(); + z.rotationY = 90; + let mr = z.addComponent(MeshRenderer); + mr.geometry = line; + let mat = mr.material = new UnLitMaterial(); + console.log(mat) + mat.baseColor = new Color(0, 1, 0, 0.5); + mat.blendMode = BlendMode.ADD; + mat.castReflection = false; + mat.topology = 'line-list'; + this.addChild(z) + } + } +} diff --git a/src/util/transformUtil/Object3DTransformTools.ts b/src/util/transformUtil/Object3DTransformTools.ts index 64ac4028..79932de4 100644 --- a/src/util/transformUtil/Object3DTransformTools.ts +++ b/src/util/transformUtil/Object3DTransformTools.ts @@ -12,11 +12,6 @@ import { Engine3D } from "../../Engine3D"; import { KeyCode } from "../../event/KeyCode"; - - - - - /** * Object3D transform controller * @group Controller From 4d6a8387310381d158fc13bc168cc7482cc656b3 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 7 Aug 2024 23:16:10 +0800 Subject: [PATCH 14/25] fix(shadow): fix acceptShadow --- samples/utils/GUIUtil.ts | 2 +- src/assets/shader/materials/program/ShadowMapping_frag.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/utils/GUIUtil.ts b/samples/utils/GUIUtil.ts index 3493d803..a1d54400 100644 --- a/samples/utils/GUIUtil.ts +++ b/samples/utils/GUIUtil.ts @@ -555,7 +555,7 @@ export class GUIUtil { }); GUIHelp.add(mat, 'castShadow'); - + GUIHelp.add(mat, 'acceptShadow'); open && GUIHelp.open(); GUIHelp.endFolder(); diff --git a/src/assets/shader/materials/program/ShadowMapping_frag.ts b/src/assets/shader/materials/program/ShadowMapping_frag.ts index 91f45280..91d9031d 100644 --- a/src/assets/shader/materials/program/ShadowMapping_frag.ts +++ b/src/assets/shader/materials/program/ShadowMapping_frag.ts @@ -142,6 +142,7 @@ export let ShadowMapping_frag: string = /*wgsl*/ ` } visibility /= totalWeight; } + #endif return vec4(visibility, isOutSideArea, varying_shadowUV); } From 7c18db5157a0001c9f056e6c7a158e62ff5f0e2b Mon Sep 17 00:00:00 2001 From: MorenID <51897387+ID-Emmett@users.noreply.github.com> Date: Wed, 14 Aug 2024 07:18:09 +0800 Subject: [PATCH 15/25] feat(physics): Refactor physics plugin with extensive enhancements and new features (#440) * feat(physics): Refactor physics plugin with extensive enhancements and new features * refator: remove _initedFunctions, use wait() * chore: opt update timeStep * chore: use enable to pause/resume onUpdate * chore: build all packages in CI test * chore: fix physic build script * chore: update cloth texture * chore --------- Co-authored-by: ShuangLiu --- package.json | 9 +- packages/ammo/ammo.d.ts | 15 + packages/ammo/package.json | 2 +- packages/physics/ClothSoftBody.ts | 124 ---- packages/physics/HingeConstraint.ts | 69 -- packages/physics/Physics.ts | 236 +++---- packages/physics/README.md | 2 +- packages/physics/Rigidbody.ts | 286 --------- .../physics/constraint/ConeTwistConstraint.ts | 83 +++ packages/physics/constraint/ConstraintBase.ts | 133 ++++ .../physics/constraint/FixedConstraint.ts | 31 + .../constraint/Generic6DofConstraint.ts | 93 +++ .../constraint/Generic6DofSpringConstraint.ts | 180 ++++++ .../physics/constraint/HingeConstraint.ts | 113 ++++ .../constraint/PointToPointConstraint.ts | 20 + .../physics/constraint/SliderConstraint.ts | 138 ++++ packages/physics/index.ts | 22 +- packages/physics/package.json | 8 +- packages/physics/rigidbody/GhostTrigger.ts | 178 ++++++ packages/physics/rigidbody/Rigidbody.ts | 598 ++++++++++++++++++ packages/physics/rigidbody/RigidbodyEnum.ts | 79 +++ .../physics/rigidbody/RigidbodyExpansion.ts | 132 ++++ packages/physics/softbody/ClothSoftbody.ts | 397 ++++++++++++ packages/physics/utils/CollisionShapeUtil.ts | 411 ++++++++++++ .../physics/utils/ContactProcessedUtil.ts | 188 ++++++ packages/physics/utils/RigidBodyMapping.ts | 68 ++ packages/physics/utils/RigidBodyUtil.ts | 169 +++++ packages/physics/utils/TempPhyMath.ts | 105 +++ .../physics/visualDebug/DebugDrawModeEnum.ts | 90 +++ .../physics/visualDebug/PhysicsDebugDrawer.ts | 172 +++++ samples/physics/Sample_Dominoes.ts | 170 +++++ samples/physics/Sample_EatTheBox.ts | 35 +- samples/physics/Sample_MultipleConstraints.ts | 324 ++++++++++ samples/physics/Sample_MultipleShapes.ts | 308 +++++++++ samples/physics/Sample_PhysicsCar.ts | 14 +- samples/physics/Sample_ShootTheBox.ts | 8 +- samples/physics/Sample_dofSpringConstraint.ts | 225 +++++++ src/util/GridObject.ts | 1 - 38 files changed, 4597 insertions(+), 639 deletions(-) delete mode 100644 packages/physics/ClothSoftBody.ts delete mode 100644 packages/physics/HingeConstraint.ts delete mode 100644 packages/physics/Rigidbody.ts create mode 100644 packages/physics/constraint/ConeTwistConstraint.ts create mode 100644 packages/physics/constraint/ConstraintBase.ts create mode 100644 packages/physics/constraint/FixedConstraint.ts create mode 100644 packages/physics/constraint/Generic6DofConstraint.ts create mode 100644 packages/physics/constraint/Generic6DofSpringConstraint.ts create mode 100644 packages/physics/constraint/HingeConstraint.ts create mode 100644 packages/physics/constraint/PointToPointConstraint.ts create mode 100644 packages/physics/constraint/SliderConstraint.ts create mode 100644 packages/physics/rigidbody/GhostTrigger.ts create mode 100644 packages/physics/rigidbody/Rigidbody.ts create mode 100644 packages/physics/rigidbody/RigidbodyEnum.ts create mode 100644 packages/physics/rigidbody/RigidbodyExpansion.ts create mode 100644 packages/physics/softbody/ClothSoftbody.ts create mode 100644 packages/physics/utils/CollisionShapeUtil.ts create mode 100644 packages/physics/utils/ContactProcessedUtil.ts create mode 100644 packages/physics/utils/RigidBodyMapping.ts create mode 100644 packages/physics/utils/RigidBodyUtil.ts create mode 100644 packages/physics/utils/TempPhyMath.ts create mode 100644 packages/physics/visualDebug/DebugDrawModeEnum.ts create mode 100644 packages/physics/visualDebug/PhysicsDebugDrawer.ts create mode 100644 samples/physics/Sample_Dominoes.ts create mode 100644 samples/physics/Sample_MultipleConstraints.ts create mode 100644 samples/physics/Sample_MultipleShapes.ts create mode 100644 samples/physics/Sample_dofSpringConstraint.ts diff --git a/package.json b/package.json index 89a4a284..3f628424 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,14 @@ "scripts": { "dev": "vite", "build": "tsc --p tsconfig.build.json && vite build && npm run build:types && npm run minify", - "build:test": "tsc --p tsconfig.build.json && vite build", + "build:test": "tsc --p tsconfig.build.json && vite build && npm run build:packages", + "build:packages": "npm run build:physics && npm run build:media && npm run build:stats && npm run build:particle && npm run build:graphic && npm run build:effect", + "build:physics": "cd packages/physics && npm run build", + "build:media": "cd packages/media-extention && npm run build", + "build:stats": "cd packages/stats && npm run build", + "build:particle": "cd packages/particle && npm run build", + "build:graphic": "cd packages/graphic && npm run build", + "build:effect": "cd packages/effect && npm run build", "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json && rm -rf dist/packages && mv dist/src dist/types", "minify": "./node_modules/vite/node_modules/.bin/esbuild dist/orillusion.es.max.js --sourcemap --minify --outfile=dist/orillusion.es.js && ./node_modules/vite/node_modules/.bin/esbuild dist/orillusion.umd.max.js --minify --sourcemap --outfile=dist/orillusion.umd.js", "test": "electron test/ci/main.js", diff --git a/packages/ammo/ammo.d.ts b/packages/ammo/ammo.d.ts index ff259acf..be37de25 100644 --- a/packages/ammo/ammo.d.ts +++ b/packages/ammo/ammo.d.ts @@ -7,6 +7,11 @@ declare function Ammo(target?: T): Promise; * Ammo.js by Bullet2 */ declare module Ammo { + function wrapPointer(ptr: number, type: new (...args: any[]) => T): T; + function addFunction(func: Function): number; + function getPointer(object: any): number; + function UTF8ToString(warningString: number): string; + function castObject T>(object: any, type: C): InstanceType; function destroy(obj: any): void; function _malloc(size: number): number; function _free(ptr: number): void; @@ -120,6 +125,7 @@ declare module Ammo { set_m_graphicsWorldTrans(m_graphicsWorldTrans: btTransform): void; } class btCollisionObject { + kB: number; setAnisotropicFriction(anisotropicFriction: btVector3, frictionMode: number): void; getCollisionShape(): btCollisionShape; setContactProcessingThreshold(contactProcessingThreshold: number): void; @@ -410,6 +416,10 @@ declare module Ammo { class btBvhTriangleMeshShape extends btTriangleMeshShape { constructor(meshInterface: btStridingMeshInterface, useQuantizedAabbCompression: boolean, buildBvh?: boolean); } + class btGImpactMeshShape extends btTriangleMeshShape { + constructor(meshInterface: btStridingMeshInterface); + updateBound(): void; + } class btHeightfieldTerrainShape extends btConcaveShape { constructor(heightStickWidth: number, heightStickLength: number, heightfieldData: unknown, heightScale: number, minHeight: number, maxHeight: number, upAxis: number, hdt: PHY_ScalarType, flipQuadEdges: boolean); setMargin(margin: number): void; @@ -598,6 +608,11 @@ declare module Ammo { setUpperLinLimit(upperLimit: number): void; setLowerAngLimit(lowerAngLimit: number): void; setUpperAngLimit(upperAngLimit: number): void; + getLinearPos(): number; + getAngularPos(): number; + setTargetLinMotorVelocity(velocity: number): void; + setPoweredLinMotor(onOff: boolean): void; + setMaxLinMotorForce(force: number): void; } class btFixedConstraint extends btTypedConstraint { constructor(rbA: btRigidBody, rbB: btRigidBody, frameInA: btTransform, frameInB: btTransform); diff --git a/packages/ammo/package.json b/packages/ammo/package.json index 3055ea0e..398c4e3c 100644 --- a/packages/ammo/package.json +++ b/packages/ammo/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/ammo", - "version": "0.2.0", + "version": "0.2.1", "author": "Orillusion", "description": "Orillusion WebGPU Engine", "main": "ammo.js", diff --git a/packages/physics/ClothSoftBody.ts b/packages/physics/ClothSoftBody.ts deleted file mode 100644 index 4d1b86a7..00000000 --- a/packages/physics/ClothSoftBody.ts +++ /dev/null @@ -1,124 +0,0 @@ -import Ammo from '@orillusion/ammo'; -import { ComponentBase, Vector3, PlaneGeometry } from '@orillusion/core' -import { Physics } from './Physics'; -/** - * @internal - * @group Plugin - */ -export class ClothSoftBody extends ComponentBase { - private _mass: number = 0.01; - - private _planeGeo: PlaneGeometry; - - private _clothCorner00: Vector3; - - private _clothCorner01: Vector3; - - private _clothCorner10: Vector3; - - private _clothCorner11: Vector3; - - private _softBody: Ammo.btSoftBody; - - // get setter clothcorner - public get clothCorner00(): Vector3 { - return this._clothCorner00; - } - - public set clothCorner00(value: Vector3) { - this._clothCorner00 = value; - } - - public set clothCorner01(value: Vector3) { - this._clothCorner01 = value; - } - - public set clothCorner10(value: Vector3) { - this._clothCorner10 = value; - } - - public get clothCorner11(): Vector3 { - return this._clothCorner11; - } - - public set clothCorner11(value: Vector3) { - this._clothCorner11 = value; - } - - public get planeGeometry(): PlaneGeometry { - return this._planeGeo; - } - - public set planeGeometry(value: PlaneGeometry) { - this._planeGeo = value; - } - - public get mass(): number { - return this._mass; - } - - public set mass(value: number) { - this._mass = value; - } - - start(): void { - if (!this._planeGeo) { - console.error('cloth need planeGeometry'); - return; - } - - if (!this._clothCorner00) { - console.error('cloth need clothCorner00'); - return; - } - - if (!this._clothCorner01) { - console.error('cloth need clothCorner01'); - return; - } - - if (!this._clothCorner10) { - console.error('cloth need clothCorner10'); - return; - } - - if (!this.clothCorner11) { - console.error('cloth need clothCorner11'); - return; - } - var cc00 = new Ammo.btVector3(this._clothCorner00.x, this._clothCorner00.y, this._clothCorner00.z); - var cc01 = new Ammo.btVector3(this._clothCorner01.x, this._clothCorner01.y, this._clothCorner01.z); - var cc10 = new Ammo.btVector3(this._clothCorner10.x, this._clothCorner10.y, this._clothCorner10.z); - var cc11 = new Ammo.btVector3(this._clothCorner11.x, this._clothCorner11.y, this._clothCorner11.z); - - var softBodyHelpers = new Ammo.btSoftBodyHelpers(); - var softBody = softBodyHelpers.CreatePatch( - (Physics.world as Ammo.btSoftRigidDynamicsWorld).getWorldInfo(), - //todo calc clothCorner; - cc00, - cc01, - cc10, - cc11, - this._planeGeo.width, - this._planeGeo.height, - 0, - true, - ); - var sbconfig = softBody.get_m_cfg(); - sbconfig.set_viterations(10); - sbconfig.set_piterations(10); - softBody.setTotalMass(0.9, false); - // Ammo.castObject(softBody, Ammo.btCollisionObject).getCollisionShape().setMargin(0.05); - (Physics.world as Ammo.btSoftRigidDynamicsWorld).addSoftBody(softBody, 1, -1); - this._softBody = softBody; - // this._planeGeo.indexBuffer.buffer - - // var c00 = new - - - } - - public onUpdate(): void { - //todo update geo vecs - } -} diff --git a/packages/physics/HingeConstraint.ts b/packages/physics/HingeConstraint.ts deleted file mode 100644 index ed0b6163..00000000 --- a/packages/physics/HingeConstraint.ts +++ /dev/null @@ -1,69 +0,0 @@ -import Ammo from '@orillusion/ammo'; -import { ComponentBase, Vector3 } from '@orillusion/core' -import { Physics } from './Physics'; -import { Rigidbody } from './Rigidbody'; -/** - * @internal - * @group Plugin - */ -export class HingeConstraint extends ComponentBase { - private _targetRigidbody: Rigidbody; - public pivotSelf: Vector3 = new Vector3(); - public pivotTarget: Vector3 = new Vector3(); - public axisSelf: Vector3 = new Vector3(0, 1, 0); - public axisTarget: Vector3 = new Vector3(0, 1, 0); - private _hinge: Ammo.btHingeConstraint; - - start(): void { - var selfRb = this.object3D.getComponent(Rigidbody); - if (selfRb == null) { - console.error('HingeConstraint need rigidbody'); - return; - } - - if (this._targetRigidbody == null) { - console.error('HingeConstraint need target rigidbody'); - return; - } - - let canStart = true; - if (!selfRb.btRigidbodyInited) { - selfRb.addInitedFunction(this.start, this); - canStart = false; - } - if (!this._targetRigidbody.btRigidbodyInited) { - this._targetRigidbody.addInitedFunction(this.start, this); - canStart = false; - } - - // console.log("hinge true start init"); - - let axisSelf = new Ammo.btVector3(this.axisSelf.x, this.axisSelf.y, this.axisSelf.z); - let axisTarget = new Ammo.btVector3(this.axisTarget.x, this.axisTarget.y, this.axisTarget.z); - - if (!canStart) { - return; - } - let pa = new Ammo.btVector3(this.pivotSelf.x, this.pivotSelf.y, this.pivotSelf.z); - let pb = new Ammo.btVector3(this.pivotTarget.x, this.pivotTarget.y, this.pivotTarget.z); - let hinge = new Ammo.btHingeConstraint(selfRb.btRigidbody, this._targetRigidbody.btRigidbody, pa, pb, axisSelf, axisTarget, true); - this._hinge = hinge; - Physics.world.addConstraint(hinge, true); - } - - public get hinge(): Ammo.btHingeConstraint { - return this._hinge; - } - - public get targetRigidbody(): Rigidbody { - return this._targetRigidbody; - } - - public set targetRigidbody(value: Rigidbody) { - this._targetRigidbody = value; - } - - public destroy(force?: boolean): void { - super.destroy(force); - } -} diff --git a/packages/physics/Physics.ts b/packages/physics/Physics.ts index 115310f4..fa16f89c 100644 --- a/packages/physics/Physics.ts +++ b/packages/physics/Physics.ts @@ -1,165 +1,169 @@ import Ammo from '@orillusion/ammo'; -import {BoundingBox, Vector3, Time} from '@orillusion/core' -import { Rigidbody } from './Rigidbody'; +import { Vector3, Time, BoundingBox, Object3D, Quaternion } from '@orillusion/core'; +import { ContactProcessedUtil } from './utils/ContactProcessedUtil'; +import { RigidBodyUtil } from './utils/RigidBodyUtil'; +import { TempPhyMath } from './utils/TempPhyMath'; +import { Rigidbody } from './rigidbody/Rigidbody'; +import { PhysicsDebugDrawer } from './visualDebug/PhysicsDebugDrawer'; +import { DebugDrawerOptions } from './visualDebug/DebugDrawModeEnum'; -/** - * Physics Engine - * @group Plugin - */ class _Physics { private _world: Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld; + private _isInited: boolean = false; private _isStop: boolean = false; private _gravity: Vector3 = new Vector3(0, -9.8, 0); - private _gravityEnabled: boolean = true; - private _maxSubSteps: number = 10; - private _fixedTimeStep: number = 1 / 60; - private _maxVelocity: number = 1000; - private _maxAngularVelocity: number = 1000; - private _maxForce: number = 1000; - private _maxTorque: number = 1000; - private _maxLinearCorrection: number = 0.2; - private _maxAngularCorrection: number = 0.2; - private _maxTranslation: number = 1000; - private _maxRotation: number = 1000; - private _maxSolverIterations: number = 20; - private _enableFriction: boolean = true; - private _enableCollisionEvents: boolean = true; - private _enableContinuous: boolean = true; - private _enableCCD: boolean = true; - private _enableWarmStarting: boolean = true; - private _enableTOI: boolean = true; - private _enableSAT: boolean = true; - private _enableSATNormal: boolean = true; - - private physicBound: BoundingBox; - private _isInited: boolean = false; + private _worldInfo: Ammo.btSoftBodyWorldInfo | null = null; + private _debugDrawer: PhysicsDebugDrawer; + private _physicBound: BoundingBox; + private _destroyObjectBeyondBounds: boolean; - public TEMP_TRANSFORM: Ammo.btTransform; //Temp cache, save results from body.getWorldTransform() + public readonly contactProcessedUtil = ContactProcessedUtil; + public readonly rigidBodyUtil = RigidBodyUtil; - constructor() { } + public maxSubSteps: number = 10; + public fixedTimeStep: number = 1 / 60; /** - * Init Physics Engine + * 物理调试绘制器 */ - public async init() { - await Ammo.bind(window)(Ammo); - this.TEMP_TRANSFORM = new Ammo.btTransform(); - var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration(); - var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); - var overlappingPairCache = new Ammo.btDbvtBroadphase(); - var solver = new Ammo.btSequentialImpulseConstraintSolver(); - this._world = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration); - this._world.setGravity(new Ammo.btVector3(this._gravity.x, this._gravity.y, this._gravity.z)); - this._isInited = true; - - this.physicBound = new BoundingBox(new Vector3(), new Vector3(2000, 2000, 2000)); + public get debugDrawer() { + if (!this._debugDrawer) { + console.warn('To enable debugging, configure with: Physics.initDebugDrawer'); + } + return this._debugDrawer; } - public get maxSubSteps(): number { - return this._maxSubSteps; - } - public set maxSubSteps(value: number) { - this._maxSubSteps = value; - } + public TEMP_TRANSFORM: Ammo.btTransform; // Temp cache, save results from body.getWorldTransform() - public get fixedTimeStep(): number { - return this._fixedTimeStep; - } - public set fixedTimeStep(value: number) { - this._fixedTimeStep = value; - } + /** + * 初始化物理引擎和相关配置。 + * + * @param options - 初始化选项参数对象。 + * @param options.useSoftBody - 是否启用软体模拟,目前仅支持布料软体类型。 + * @param options.physicBound - 物理边界,默认范围:2000 2000 2000,超出边界时将会销毁该刚体。 + * @param options.destroyObjectBeyondBounds - 是否在超出边界时销毁3D对象。默认 `false` 仅销毁刚体。 + */ + public async init(options: { useSoftBody?: boolean, physicBound?: Vector3, destroyObjectBeyondBounds?: boolean } = {}) { + await Ammo.bind(window)(Ammo); - public get isStop(): boolean { - return this._isStop; - } + TempPhyMath.init(); - public set isStop(value: boolean) { - this._isStop = value; - } + this.TEMP_TRANSFORM = new Ammo.btTransform(); + this.initWorld(options.useSoftBody); - public set gravity(gravity: Vector3) { - this._gravity = gravity; + this._isInited = true; + this._destroyObjectBeyondBounds = options.destroyObjectBeyondBounds; + this._physicBound = new BoundingBox(new Vector3(), options.physicBound || new Vector3(2000, 2000, 2000)); } - public get gravity(): Vector3 { - return this._gravity; - } + /** + * 初始化物理调试绘制器 + * + * @param {Graphic3D} graphic3D - Type: `Graphic3D` A graphic object used to draw lines. + * @param {DebugDrawerOptions} [options] - 调试绘制选项,用于配置物理调试绘制器。 {@link DebugDrawerOptions} + */ + public initDebugDrawer(graphic3D: Object3D, options?: DebugDrawerOptions) { + this._debugDrawer = new PhysicsDebugDrawer(this.world, graphic3D, options); + } + + private initWorld(useSoftBody: boolean) { + const collisionConfiguration = useSoftBody + ? new Ammo.btSoftBodyRigidBodyCollisionConfiguration() + : new Ammo.btDefaultCollisionConfiguration(); + const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); + const broadphase = new Ammo.btDbvtBroadphase(); + const solver = new Ammo.btSequentialImpulseConstraintSolver(); + + if (useSoftBody) { + const softBodySolver = new Ammo.btDefaultSoftBodySolver(); + this._world = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver); + this._worldInfo = (this.world as Ammo.btSoftRigidDynamicsWorld).getWorldInfo(); + this._worldInfo.set_m_broadphase(broadphase); + this._worldInfo.set_m_dispatcher(dispatcher); + this._worldInfo.set_m_gravity(TempPhyMath.toBtVec(this._gravity)); + this._worldInfo.set_air_density(1.2); + this._worldInfo.set_water_density(0); + this._worldInfo.set_water_offset(0); + this._worldInfo.set_water_normal(TempPhyMath.setBtVec(0, 0, 0)); + this._worldInfo.set_m_maxDisplacement(0.5); + } else { + this._world = new Ammo.btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); + } - public get world(): Ammo.btDiscreteDynamicsWorld { - return this._world; + this._world.setGravity(TempPhyMath.toBtVec(this._gravity)); } - private initByDefault() { - var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration(); - var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); - var overlappingPairCache = new Ammo.btDbvtBroadphase(); - var solver = new Ammo.btSequentialImpulseConstraintSolver(); - this._world = new Ammo.btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration); - this._world.setGravity(new Ammo.btVector3(this._gravity.x, this._gravity.y, this._gravity.z)); - this._isInited = true; - this.physicBound = new BoundingBox(new Vector3(), new Vector3(2000, 2000, 2000)); + /** + * 物理模拟更新 + * @param timeStep - 时间步长 + * @default Time.delta * 0.001 + */ + public update(timeStep: number = Time.delta * 0.001) { + if (!this._isInited || this.isStop) return; + this.world.stepSimulation(timeStep, this.maxSubSteps, this.fixedTimeStep); + // this.world.stepSimulation(Time.delta, 1, this.fixedTimeStep); + + this._debugDrawer?.update(); } - private initBySoft() { - var collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration(); - var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); - var broadphase = new Ammo.btDbvtBroadphase(); - var solver = new Ammo.btSequentialImpulseConstraintSolver(); - var softBodySolver = new Ammo.btDefaultSoftBodySolver(); - this._world = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver); - this._world.setGravity(new Ammo.btVector3(this._gravity.x, this._gravity.y, this._gravity.z)); - this._isInited = true; - this.physicBound = new BoundingBox(new Vector3(), new Vector3(2000, 2000, 2000)); + public get world(): Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld { + return this._world; } public get isInited(): boolean { return this._isInited; } - private __updateWithDelta(time:number ) { - if(!this._isInited) return; - if(this.isStop) return; - this._world.stepSimulation(time); + public set isStop(value: boolean) { + this._isStop = value; } - private __updateWithFixedTimeStep(time:number, maxSubSteps:number, fixedTimeStep:number ) { - if(!this._isInited) return; - if(this.isStop) return; - this.world.stepSimulation(time, maxSubSteps, fixedTimeStep); + public get isStop() { + return this._isStop; } + public set gravity(value: Vector3) { + this._gravity.copyFrom(value); + this._world?.setGravity(TempPhyMath.toBtVec(value)); // 设置刚体物理重力 + this._worldInfo?.set_m_gravity(TempPhyMath.toBtVec(value)); // 设置软体物理重力 + } - public update() { - if (!this._isInited) { - return; - } - if (this.isStop) return; - - // let fix = Math.max(this._fixedTimeStep, Time.detail); - - this.__updateWithFixedTimeStep(Time.delta, 1, this._fixedTimeStep); - // this._world.stepSimulation(Time.delta, 1, this._fixedTimeStep); + public get gravity(): Vector3 { + return this._gravity; } - public addRigidbody(rigidBody: Rigidbody) { - this._world.addRigidBody(rigidBody.btRigidbody); + public get worldInfo(): Ammo.btSoftBodyWorldInfo { + return this._worldInfo; } - public removeRigidbody(rigidBody: Rigidbody) { - this._world.removeRigidBody(rigidBody.btRigidbody); + public get isSoftBodyWord() { + return this._world instanceof Ammo.btSoftRigidDynamicsWorld; } - checkBound(body: Rigidbody) { + public checkBound(body: Rigidbody) { if (body) { let wp = body.transform.worldPosition; - let inside = this.physicBound.containsPoint(wp); + let inside = this._physicBound.containsPoint(wp); if (!inside) { - body.btRigidbody.activate(false); - // this._world.removeRigidBody(body.btRigidbody); - body.destroy(); + if (this._destroyObjectBeyondBounds) { + body.object3D.destroy(); + } else { + body.btRigidbody.activate(false); + body.destroy(); + } } } } + + /** + * 将物理对象的位置和旋转同步至三维对象 + * @param object3D - 三维对象 + * @param tm - 物理对象变换 + */ + public syncGraphic(object3D: Object3D, tm: Ammo.btTransform): void { + object3D.localPosition = TempPhyMath.fromBtVec(tm.getOrigin(), Vector3.HELP_0); + object3D.localQuaternion = TempPhyMath.fromBtQua(tm.getRotation(), Quaternion.HELP_0); + } } /** @@ -173,4 +177,4 @@ class _Physics { * @internal */ export let Physics = new _Physics(); -export {Ammo} +export { Ammo }; diff --git a/packages/physics/README.md b/packages/physics/README.md index 0e9422cc..779eb0c0 100644 --- a/packages/physics/README.md +++ b/packages/physics/README.md @@ -7,7 +7,7 @@ npm install @orillusion/physics --save ``` ```ts import { Scene3D } from "@orillusion/core" -import { Stats } from "@orillusion/physics" +import { Physics } from "@orillusion/physics" ``` Or access Global build from CDN diff --git a/packages/physics/Rigidbody.ts b/packages/physics/Rigidbody.ts deleted file mode 100644 index 5f5b52c9..00000000 --- a/packages/physics/Rigidbody.ts +++ /dev/null @@ -1,286 +0,0 @@ -import Ammo from '@orillusion/ammo'; -import { Vector3, BoxColliderShape, CapsuleColliderShape, ColliderComponent, ComponentBase, MeshColliderShape, Quaternion, SphereColliderShape } from '@orillusion/core' -import { Physics } from './Physics'; - -enum CollisionFlags { - STATIC_OBJECT = 1, - KINEMATIC_OBJECT = 2, - NO_CONTACT_RESPONSE = 4, - CUSTOM_MATERIAL_CALLBACK = 8, - CHARACTER_OBJECT = 16, - DISABLE_VISUALIZE_OBJECT = 32, - DISABLE_SPU_COLLISION_PROCESSING = 64, - HAS_CONTACT_STIFFNESS_DAMPING = 128, - HAS_CUSTOM_DEBUG_RENDERING_COLOR = 256, - HAS_FRICTION_ANCHOR = 512, - HAS_COLLISION_SOUND_TRIGGER = 1024 -} - -enum CollisionObjectTypes { - COLLISION_OBJECT = 1, - RIGID_BODY = 2, - GHOST_OBJECT = 4, - SOFT_BODY = 8, - HF_FLUID = 16, - USER_TYPE = 32, - FEATHERSTONE_LINK = 64 -} - -/** - * Rigidbody Component - * Rigid bodies can endow game objects with physical properties, allowing them to be controlled by the physics system and subjected to forces and torques, thus achieving realistic motion effects. - * @group Components - */ -export class Rigidbody extends ComponentBase { - private _mass: number = 0.01; - private _velocity: Vector3 = new Vector3(); - private _angularVelocity: Vector3 = new Vector3(); - private _force: Vector3 = new Vector3(); - private _useGravity: boolean = true; - private _isKinematic: boolean = false; - private _isStatic: boolean = false; - private _isTrigger: boolean = false; - private _btRigidbody: Ammo.btRigidBody; - private _btRigidbodyInited: boolean = false; - private _friction: number = 0.6; - private _rollingFriction: number = 0.1; - private _restitution: number = 0.8 - - private _initedFunctions: { fun: Function; thisObj: Object }[] = []; - - init(): void { - - } - - public start(): void { - if (!this.object3D.getComponent(ColliderComponent)) { - console.error('rigidbody need collider'); - return; - } - this.initRigidbody(); - } - - /** - * Get friction value - */ - public get friction() { - return this._friction; - } - /** - * Set friction value - */ - public set friction(value: number) { - this._friction = value; - if (this._btRigidbody) this._btRigidbody.setFriction(value); - } - /** - * Get rolling friction value - */ - public get rollingFriction(): number { - return this._rollingFriction; - } - /** - * Set rolling friction value - */ - public set rollingFriction(value: number) { - this._rollingFriction = value; - if (this._btRigidbody) this._btRigidbody.setRollingFriction(value); - } - /** - * Get restitution value - */ - public get restitution(): number { - return this._restitution; - } - /** - * Set restitution value - */ - public set restitution(value: number) { - this._restitution = value; - if (this._btRigidbody) this._btRigidbody.setRestitution(value); - } - /** - * Check if rigidbody inited - */ - public get btRigidbodyInited(): boolean { - return this._btRigidbodyInited; - } - - private addAmmoRigidbody(): void { - var shape = this.getPhysicShape(); - var btTransform = new Ammo.btTransform(); - btTransform.setIdentity(); - var localInertia = new Ammo.btVector3(0, 0, 0); - - shape.calculateLocalInertia(this.mass, localInertia); - - btTransform.setOrigin(new Ammo.btVector3(this.object3D.x, this.object3D.y, this.object3D.z)); - let t = this.object3D.transform; - - Quaternion.HELP_0.fromEulerAngles(t.rotationX, t.rotationY, t.rotationZ); - let btq = new Ammo.btQuaternion(Quaternion.HELP_0.x, Quaternion.HELP_0.y, Quaternion.HELP_0.z, Quaternion.HELP_0.w); - btTransform.setRotation(btq); - - var motionState = new Ammo.btDefaultMotionState(btTransform); - var rbInfo = new Ammo.btRigidBodyConstructionInfo(this.mass, motionState, shape, localInertia); - - this._btRigidbody = new Ammo.btRigidBody(rbInfo); - this._btRigidbody.setRestitution(this.restitution); - this.btRigidbody.setFriction(this.friction); - this.btRigidbody.setRollingFriction(this.rollingFriction); - Physics.addRigidbody(this); - } - private initRigidbody(): void { - this.addAmmoRigidbody(); - - for (let i = 0; i < this._initedFunctions.length; i++) { - let fun = this._initedFunctions[i]; - fun.fun.call(fun.thisObj); - } - this._btRigidbodyInited = true; - } - - /** - * Add init callback - * @param fun callback function - * @param thisObj this - */ - public addInitedFunction(fun: Function, thisObj: Object) { - this._initedFunctions.push({ fun: fun, thisObj: thisObj }); - } - /** - * Remove init callback - * @param fun callback function - * @param thisObj this - */ - public removeInitedFunction(fun: Function, thisObj: Object) { - for (let i = 0; i < this._initedFunctions.length; i++) { - let item = this._initedFunctions[i]; - if (item.fun === fun && item.thisObj === thisObj) { - this._initedFunctions.splice(i, 1); - break; - } - } - } - - private getPhysicShape() { - let collider = this.object3D.getComponent(ColliderComponent); - let colliderShape = collider.shape; - - var shape: Ammo.btCollisionShape; - - if (colliderShape instanceof BoxColliderShape) { - shape = new Ammo.btBoxShape(new Ammo.btVector3(colliderShape.halfSize.x, colliderShape.halfSize.y, colliderShape.halfSize.z)); - } else if (colliderShape instanceof CapsuleColliderShape) { - shape = new Ammo.btCapsuleShape(colliderShape.radius, colliderShape.height); - } else if (colliderShape instanceof MeshColliderShape) { - // let triangleMeshShape = new Ammo.btTriangleMeshShape(); - } else if (colliderShape instanceof SphereColliderShape) { - shape = new Ammo.btSphereShape(colliderShape.radius); - } - return shape; - } - - /** - * Return internal Ammo.btRigidBody - */ - public get btRigidbody(): Ammo.btRigidBody { - return this._btRigidbody; - } - - onUpdate(): void { - if (this._btRigidbody && this._btRigidbody.getMotionState()) { - this._btRigidbody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); - - this.transform.x = Physics.TEMP_TRANSFORM.getOrigin().x(); - this.transform.y = Physics.TEMP_TRANSFORM.getOrigin().y(); - this.transform.z = Physics.TEMP_TRANSFORM.getOrigin().z(); - - let q = Quaternion.HELP_0; - q.set(Physics.TEMP_TRANSFORM.getRotation().x(), Physics.TEMP_TRANSFORM.getRotation().y(), Physics.TEMP_TRANSFORM.getRotation().z(), Physics.TEMP_TRANSFORM.getRotation().w()); - - this.object3D.transform.localRotQuat = q; - - Physics.checkBound(this); - } - } - - public destroy(force?: boolean): void { - Physics.removeRigidbody(this); - this._initedFunctions = null; - super.destroy(force); - } - - /** - * Get mass value。 - */ - public get mass(): number { - return this._mass; - } - /** - * Set mass value。 - */ - public set mass(value: number) { - this._mass = value; - if (this._btRigidbody) { - Physics.world.removeRigidBody(this._btRigidbody); - this.addAmmoRigidbody(); - // console.log("setMassProps", "mass: " + value, "flag: " + this._btRigidbody.getCollisionFlags()); - // this._btRigidbody.setMassProps(value, new Ammo.btVector3(0, 0, 0)); - // this._btRigidbody.setCollisionFlags(this._btRigidbody.getCollisionFlags()); - // console.log("setMassProps", "mass: " + value, "flag: " + this._btRigidbody.getCollisionFlags()); - // if(this.mass <= 0) { - // this._btRigidbody.setLinearVelocity(new Ammo.btVector3(0, 0, 0)); - // this._btRigidbody.setAngularVelocity(new Ammo.btVector3(0, 0, 0)); - // } - } - } - /** - * Get velocity value of current object - */ - public get velocity(): Vector3 { - return this._velocity; - } - /** - * Set velocity value of current object - */ - public set velocity(value: Vector3) { - this._velocity = value.clone(); - if (this._btRigidbody) { - this._btRigidbody.applyForce(new Ammo.btVector3(value.x, value.y, value.z), new Ammo.btVector3(0, 0, 0)); - } - } - /** - * Get the angular velocity value of current object - */ - public get angularVelocity(): Vector3 { - return this._angularVelocity; - } - - /** - * Set the angular velocity value of current object - */ - public set angularVelocity(value: Vector3) { - this._angularVelocity = value; - } - /** - * Check if the rigidbody affect physics system - */ - public get isKinematic(): boolean { - return this._isKinematic; - } - /** - * Set if the rigidbody affect physics system - */ - public set isKinematic(value: boolean) { - this._isKinematic = value; - } - - public get isTrigger(): boolean { - return this._isTrigger; - } - - public set isTrigger(value: boolean) { - this._isTrigger = value; - } -} diff --git a/packages/physics/constraint/ConeTwistConstraint.ts b/packages/physics/constraint/ConeTwistConstraint.ts new file mode 100644 index 00000000..0dc11079 --- /dev/null +++ b/packages/physics/constraint/ConeTwistConstraint.ts @@ -0,0 +1,83 @@ +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath' +import { ConstraintBase } from './ConstraintBase'; + +/** + * 锥形扭转约束 + */ +export class ConeTwistConstraint extends ConstraintBase { + private _twistSpan: number = Math.PI; + private _swingSpan1: number = Math.PI; + private _swingSpan2: number = Math.PI; + + /** + * 扭转角度限制,绕 X 轴的扭转范围。 + * 默认值 `Math.PI` + */ + public get twistSpan() { + return this._twistSpan; + } + public set twistSpan(value: number) { + this._twistSpan = value; + this._constraint?.setLimit(3, value); + } + + /** + * 摆动角度限制1,绕 Y 轴的摆动范围。 + * 默认值 `Math.PI` + */ + public get swingSpan1() { + return this._swingSpan1; + } + public set swingSpan1(value: number) { + this._swingSpan1 = value; + this._constraint?.setLimit(5, value); + } + + /** + * 摆动角度限制2,绕 Z 轴的摆动范围。 + * 默认值 `Math.PI` + */ + public get swingSpan2() { + return this._swingSpan2; + } + public set swingSpan2(value: number) { + this._swingSpan2 = value; + this._constraint?.setLimit(4, value); + } + + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null) { + const frameInA = TempPhyMath.toBtVec(this.pivotSelf); + const rotInA = TempPhyMath.toBtQua(this.rotationSelf); + + const transformA = Physics.TEMP_TRANSFORM; + transformA.setIdentity(); + transformA.setOrigin(frameInA); + transformA.setRotation(rotInA); + + if (targetBody) { + const frameInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + const rotInB = TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB); + const transformB = new Ammo.btTransform(); + transformB.setIdentity(); + transformB.setOrigin(frameInB); + transformB.setRotation(rotInB); + + this._constraint = new Ammo.btConeTwistConstraint(selfBody, targetBody, transformA, transformB); + Ammo.destroy(transformB); + } else { + this._constraint = new Ammo.btConeTwistConstraint(selfBody, transformA); + } + + //******************************************** + //* 当前版本 Ammo 无法设置柔软度/偏差/松弛度 + //* 索引 3 是 m_twistSpan axe X + //* 索引 4 是 m_swingSpan2 axe Z + //* 索引 5 是 m_swingSpan1 axe Y + //******************************************** + + this._constraint.setLimit(3, this.twistSpan); + this._constraint.setLimit(5, this.swingSpan1); + this._constraint.setLimit(4, this.swingSpan2); + } +} diff --git a/packages/physics/constraint/ConstraintBase.ts b/packages/physics/constraint/ConstraintBase.ts new file mode 100644 index 00000000..b9d1df52 --- /dev/null +++ b/packages/physics/constraint/ConstraintBase.ts @@ -0,0 +1,133 @@ +import { ComponentBase, Vector3, Quaternion } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { Rigidbody } from '../rigidbody/Rigidbody'; + +/** + * 约束基类 + */ +export class ConstraintBase extends ComponentBase { + protected _targetRigidbody: Rigidbody; + protected _constraint: T; + private _initResolve!: () => void; + private _initializationPromise: Promise = new Promise(r => this._initResolve = r); + private _breakingThreshold: number; + + /** + * The pivot point for the self body + * `FrameInA Origin` + */ + public pivotSelf: Vector3 = new Vector3(); + /** + * The pivot point for the target body + * `FrameInB Origin` + */ + public pivotTarget: Vector3 = new Vector3(); + /** + * The rotation for the self body + * `FrameInA Rotation` + */ + public rotationSelf: Quaternion = new Quaternion(); + /** + * The rotation for the target body + * `FrameInB Rotation` + */ + public rotationTarget: Quaternion = new Quaternion(); + + public disableCollisionsBetweenLinkedBodies: boolean = true; + + /** + * 断裂脉冲阈值,值越大,约束越不易断裂。 + */ + public get breakingThreshold() { + return this._breakingThreshold; + } + + public set breakingThreshold(value: number) { + this._breakingThreshold = value; + this._constraint?.setBreakingImpulseThreshold(value); + } + + async start() { + const selfRb = this.object3D.getComponent(Rigidbody); + if (!selfRb) { + throw new Error(`${this.constructor.name} requires a rigidbody on the object.`); + } + + // 确保刚体初始化完成 + if (!selfRb.btBodyInited) { + await selfRb.wait() + } + + if (this._targetRigidbody && !this._targetRigidbody.btBodyInited) { + await this._targetRigidbody.wait() + } + + // 创建约束 + this.createConstraint(selfRb.btRigidbody, this._targetRigidbody?.btRigidbody); + + if (this._constraint) { + if (this._breakingThreshold != null) { + this._constraint.setBreakingImpulseThreshold(this._breakingThreshold); + } + Physics.world.addConstraint(this._constraint, this.disableCollisionsBetweenLinkedBodies); + this._initResolve(); + } + } + + /** + * 子类实现具体的约束创建逻辑 + * @param selfBody + * @param targetBody + */ + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null) { } + + /** + * 获取约束实例 + */ + public get constraint(): T { + if (!this._constraint) { + console.warn('Constraint has not been initialized. Please use wait() to get the constraint instance asynchronously.'); + } + return this._constraint; + } + + /** + * 异步获取完成初始化的约束实例 + */ + public async wait(): Promise { + await this._initializationPromise; + return this._constraint!; + } + + /** + * 重置约束,销毁当前约束实例后重新创建并返回新的约束实例 + */ + public async resetConstraint(): Promise { + if (this._constraint) { + Physics.rigidBodyUtil.destroyConstraint(this._constraint) + this._constraint = null; + + await this.start(); + return this._constraint!; + } + console.warn('No constraint to reset.'); + } + + /** + * 目标刚体组件 + */ + public get targetRigidbody(): Rigidbody { + return this._targetRigidbody; + } + + public set targetRigidbody(value: Rigidbody) { + this._targetRigidbody = value; + } + + public destroy(force?: boolean): void { + Physics.rigidBodyUtil.destroyConstraint(this._constraint); + this._constraint = null; + this._targetRigidbody = null; + super.destroy(force); + } +} diff --git a/packages/physics/constraint/FixedConstraint.ts b/packages/physics/constraint/FixedConstraint.ts new file mode 100644 index 00000000..e31cc207 --- /dev/null +++ b/packages/physics/constraint/FixedConstraint.ts @@ -0,0 +1,31 @@ +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 固定约束 + */ +export class FixedConstraint extends ConstraintBase { + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null): void { + if (!targetBody) throw new Error('FixedConstraint requires a target rigidbody'); + + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf); + const rotInA = TempPhyMath.toBtQua(this.rotationSelf); + const frameInA = Physics.TEMP_TRANSFORM; + frameInA.setIdentity(); + frameInA.setOrigin(pivotInA); + frameInA.setRotation(rotInA); + + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + const rotInB = TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB); + const frameInB = new Ammo.btTransform(); + frameInB.setIdentity(); + frameInB.setOrigin(pivotInB); + frameInB.setRotation(rotInB); + + this._constraint = new Ammo.btFixedConstraint(selfBody, targetBody, frameInA, frameInB); + Ammo.destroy(frameInB); + } + + +} \ No newline at end of file diff --git a/packages/physics/constraint/Generic6DofConstraint.ts b/packages/physics/constraint/Generic6DofConstraint.ts new file mode 100644 index 00000000..c10dd313 --- /dev/null +++ b/packages/physics/constraint/Generic6DofConstraint.ts @@ -0,0 +1,93 @@ +import { Vector3 } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 通用六自由度约束 + */ +export class Generic6DofConstraint extends ConstraintBase { + private _linearLowerLimit: Vector3 = new Vector3(-1e30, -1e30, -1e30); + private _linearUpperLimit: Vector3 = new Vector3(1e30, 1e30, 1e30); + private _angularLowerLimit: Vector3 = new Vector3(-Math.PI, -Math.PI, -Math.PI); + private _angularUpperLimit: Vector3 = new Vector3(Math.PI, Math.PI, Math.PI); + + /** + * default: `-1e30, -1e30, -1e30` + */ + public get linearLowerLimit(): Vector3 { + return this._linearLowerLimit; + } + public set linearLowerLimit(value: Vector3) { + this._linearLowerLimit.copyFrom(value); + this._constraint?.setLinearLowerLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `1e30, 1e30, 1e30` + */ + public get linearUpperLimit(): Vector3 { + return this._linearUpperLimit; + } + public set linearUpperLimit(value: Vector3) { + this._linearUpperLimit.copyFrom(value); + this._constraint?.setLinearUpperLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `-Math.PI, -Math.PI, -Math.PI` + */ + public get angularLowerLimit(): Vector3 { + return this._angularLowerLimit; + } + public set angularLowerLimit(value: Vector3) { + this._angularLowerLimit.copyFrom(value); + this._constraint?.setAngularLowerLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `Math.PI, Math.PI, Math.PI` + */ + public get angularUpperLimit(): Vector3 { + return this._angularUpperLimit; + } + public set angularUpperLimit(value: Vector3) { + this._angularUpperLimit.copyFrom(value); + this._constraint?.setAngularUpperLimit(TempPhyMath.toBtVec(value)); + } + + /** + * 是否使用线性参考坐标系。 + * 默认值: `true` + */ + public useLinearFrameReferenceFrame: boolean = true; + + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null): void { + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf); + const rotInA = TempPhyMath.toBtQua(this.rotationSelf); + + const frameInA = Physics.TEMP_TRANSFORM; + frameInA.setIdentity(); + frameInA.setOrigin(pivotInA); + frameInA.setRotation(rotInA); + + if (targetBody) { + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + const rotInB = TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB); + const frameInB = new Ammo.btTransform(); + frameInB.setIdentity(); + frameInB.setOrigin(pivotInB); + frameInB.setRotation(rotInB); + + this._constraint = new Ammo.btGeneric6DofConstraint(selfBody, targetBody, frameInA, frameInB, this.useLinearFrameReferenceFrame); + Ammo.destroy(frameInB); + } else { + this._constraint = new Ammo.btGeneric6DofConstraint(selfBody, frameInA, this.useLinearFrameReferenceFrame); + } + + this._constraint.setLinearLowerLimit(TempPhyMath.toBtVec(this._linearLowerLimit)); + this._constraint.setLinearUpperLimit(TempPhyMath.toBtVec(this._linearUpperLimit)); + this._constraint.setAngularLowerLimit(TempPhyMath.toBtVec(this._angularLowerLimit)); + this._constraint.setAngularUpperLimit(TempPhyMath.toBtVec(this._angularUpperLimit)); + } +} diff --git a/packages/physics/constraint/Generic6DofSpringConstraint.ts b/packages/physics/constraint/Generic6DofSpringConstraint.ts new file mode 100644 index 00000000..b15d401e --- /dev/null +++ b/packages/physics/constraint/Generic6DofSpringConstraint.ts @@ -0,0 +1,180 @@ +import { Vector3 } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 弹簧特性六自由度约束 + */ +export class Generic6DofSpringConstraint extends ConstraintBase { + private _linearLowerLimit: Vector3 = new Vector3(-1e30, -1e30, -1e30); + private _linearUpperLimit: Vector3 = new Vector3(1e30, 1e30, 1e30); + private _angularLowerLimit: Vector3 = new Vector3(-Math.PI, -Math.PI, -Math.PI); + private _angularUpperLimit: Vector3 = new Vector3(Math.PI, Math.PI, Math.PI); + + // 缓存约束配置参数 + private _springParams: { index: number, onOff: boolean }[] = []; + private _stiffnessParams: { index: number, stiffness: number }[] = []; + private _dampingParams: { index: number, damping: number }[] = []; + private _equilibriumPointParams: { index?: number, val?: number }[] = []; + + /** + * default: `-1e30, -1e30, -1e30` + */ + public get linearLowerLimit(): Vector3 { + return this._linearLowerLimit; + } + public set linearLowerLimit(value: Vector3) { + this._linearLowerLimit.copyFrom(value); + this._constraint?.setLinearLowerLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `1e30, 1e30, 1e30` + */ + public get linearUpperLimit(): Vector3 { + return this._linearUpperLimit; + } + public set linearUpperLimit(value: Vector3) { + this._linearUpperLimit.copyFrom(value); + this._constraint?.setLinearUpperLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `-Math.PI, -Math.PI, -Math.PI` + */ + public get angularLowerLimit(): Vector3 { + return this._angularLowerLimit; + } + public set angularLowerLimit(value: Vector3) { + this._angularLowerLimit.copyFrom(value); + this._constraint?.setAngularLowerLimit(TempPhyMath.toBtVec(value)); + } + + /** + * default: `Math.PI, Math.PI, Math.PI` + */ + public get angularUpperLimit(): Vector3 { + return this._angularUpperLimit; + } + public set angularUpperLimit(value: Vector3) { + this._angularUpperLimit.copyFrom(value); + this._constraint?.setAngularUpperLimit(TempPhyMath.toBtVec(value)); + } + + /** + * 启用或禁用弹簧功能。 + * @param index 弹簧的索引 + * @param onOff 是否启用 + */ + public enableSpring(index: number, onOff: boolean): void { + if (this._constraint) { + this._constraint.enableSpring(index, onOff); + } else { + this._springParams.push({ index, onOff }); + } + } + + /** + * 设置弹簧的刚度。 + * @param index 弹簧的索引 + * @param stiffness 刚度值 + */ + public setStiffness(index: number, stiffness: number): void { + if (this._constraint) { + this._constraint.setStiffness(index, stiffness); + } else { + this._stiffnessParams.push({ index, stiffness }); + } + } + + /** + * 设置弹簧的阻尼。 + * @param index 弹簧的索引 + * @param damping 阻尼值 + */ + public setDamping(index: number, damping: number): void { + if (this._constraint) { + this._constraint.setDamping(index, damping); + } else { + this._dampingParams.push({ index, damping }); + } + } + + /** + * 设置弹簧的平衡点。 + * + * @param index 弹簧的索引(可选)。如果不提供,则重置所有弹簧的平衡点。 + * @param val 平衡点值(可选)。如果提供,则设置指定弹簧的平衡点为该值。 + * + * - 不带参数时,重置所有弹簧的平衡点。 + * - 只带 `index` 参数时,设置指定弹簧的平衡点(值由系统内部处理)。 + * - 带 `index` 和 `val` 参数时,设置指定弹簧的平衡点为 `val`。 + */ + public setEquilibriumPoint(index?: number, val?: number): void { + if (this._constraint) { + if (index == undefined) { + this._constraint.setEquilibriumPoint(); + } else if (val == undefined) { + this._constraint.setEquilibriumPoint(index); + } else { + this._constraint.setEquilibriumPoint(index, val); + } + } else { + this._equilibriumPointParams.push({ index, val }); + } + } + + /** + * 是否使用线性参考坐标系。 + * 默认值 `true` + */ + public useLinearFrameReferenceFrame: boolean = true; + + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null): void { + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf); + const rotInA = TempPhyMath.toBtQua(this.rotationSelf); + + const frameInA = Physics.TEMP_TRANSFORM; + frameInA.setIdentity(); + frameInA.setOrigin(pivotInA); + frameInA.setRotation(rotInA); + + if (targetBody) { + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + const rotInB = TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB); + const frameInB = new Ammo.btTransform(); + frameInB.setIdentity(); + frameInB.setOrigin(pivotInB); + frameInB.setRotation(rotInB); + + this._constraint = new Ammo.btGeneric6DofSpringConstraint(selfBody, targetBody, frameInA, frameInB, this.useLinearFrameReferenceFrame); + Ammo.destroy(frameInB); + } else { + this._constraint = new Ammo.btGeneric6DofSpringConstraint(selfBody, frameInA, this.useLinearFrameReferenceFrame); + } + + this.setConstraint() + } + + private setConstraint() { + + // 设置线性和角度限制 + this._constraint.setLinearLowerLimit(TempPhyMath.toBtVec(this._linearLowerLimit)); + this._constraint.setLinearUpperLimit(TempPhyMath.toBtVec(this._linearUpperLimit)); + this._constraint.setAngularLowerLimit(TempPhyMath.toBtVec(this._angularLowerLimit)); + this._constraint.setAngularUpperLimit(TempPhyMath.toBtVec(this._angularUpperLimit)); + + // 应用缓存的弹簧参数 + this._springParams.forEach(param => this._constraint.enableSpring(param.index, param.onOff)); + this._stiffnessParams.forEach(param => this._constraint.setStiffness(param.index, param.stiffness)); + this._dampingParams.forEach(param => this._constraint.setDamping(param.index, param.damping)); + this._equilibriumPointParams.forEach(param => this.setEquilibriumPoint(param.index, param.val)); + + // 清空缓存 + this._springParams = []; + this._stiffnessParams = []; + this._dampingParams = []; + this._equilibriumPointParams = []; + } +} diff --git a/packages/physics/constraint/HingeConstraint.ts b/packages/physics/constraint/HingeConstraint.ts new file mode 100644 index 00000000..3b315a7a --- /dev/null +++ b/packages/physics/constraint/HingeConstraint.ts @@ -0,0 +1,113 @@ +import { Vector3 } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 铰链约束 + */ +export class HingeConstraint extends ConstraintBase { + /** + * 自身刚体上的铰链轴方向。 + * 默认值 `Vector3.UP` + */ + public axisSelf: Vector3 = Vector3.UP; + /** + * 目标刚体上的铰链轴方向。 + * 默认值 `Vector3.UP` + */ + public axisTarget: Vector3 = Vector3.UP; + /** + * 是否使用自身刚体的参考框架。 + * 默认值 `true` + */ + public useReferenceFrameA: boolean = true; + /** + * 是否使用两个刚体的变换重载方式。 + * 如果为 true,则使用两个刚体的变换作为约束的参考框架。 + * 默认值 `false` + */ + public useTwoBodiesTransformOverload: boolean = false; + + private _pendingLimits: [number, number, number, number, number?]; + private _pendingMotorConfig: [boolean, number, number]; + + /** + * 获取当前的限制参数。 + */ + public get limitInfo() { return this._pendingLimits; } + /** + * 获取当前的马达配置参数。 + */ + public get motorConfigInfo() { return this._pendingMotorConfig; } + + /** + * 设置铰链约束的旋转限制。 + * @param low - 铰链旋转的最小角度(下限)。 + * @param high - 铰链旋转的最大角度(上限)。 + * @param softness - 软限制系数,表示限制的柔软程度。值在0到1之间,1表示完全刚性。 + * @param biasFactor - 偏置因子,用于控制限制恢复力的力度。值通常在0到1之间。 + * @param relaxationFactor -(可选)松弛因子,控制限制恢复的速度。值越大,恢复越快。 + */ + public setLimit(low: number, high: number, softness: number, biasFactor: number, relaxationFactor?: number): void { + this._pendingLimits = [low, high, softness, biasFactor, relaxationFactor]; + this._constraint?.setLimit(...this._pendingLimits); + }; + + /** + * 启用或禁用角度马达。 + * @param enableMotor - 是否启用马达。 + * @param targetVelocity - 马达的目标速度。 + * @param maxMotorImpulse - 马达的最大推力。 + */ + public enableAngularMotor(enableMotor: boolean, targetVelocity: number, maxMotorImpulse: number): void { + this._pendingMotorConfig = [enableMotor, targetVelocity, maxMotorImpulse] + this._constraint?.enableAngularMotor(...this._pendingMotorConfig) + }; + + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null) { + const constraintType = !targetBody ? + 'SINGLE_BODY_TRANSFORM' : this.useTwoBodiesTransformOverload ? + 'TWO_BODIES_TRANSFORM' : 'TWO_BODIES_PIVOT'; + + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf, TempPhyMath.tmpVecA); + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + + switch (constraintType) { + case 'SINGLE_BODY_TRANSFORM': + const frameA_single = Physics.TEMP_TRANSFORM; + frameA_single.setIdentity(); + frameA_single.setOrigin(pivotInA); + frameA_single.setRotation(TempPhyMath.toBtQua(this.rotationSelf)); + + this._constraint = new Ammo.btHingeConstraint(selfBody, frameA_single, this.useReferenceFrameA); + break; + case 'TWO_BODIES_TRANSFORM': + const frameA = Physics.TEMP_TRANSFORM; + frameA.setIdentity(); + frameA.setOrigin(pivotInA); + frameA.setRotation(TempPhyMath.toBtQua(this.rotationSelf)); + + const frameB = new Ammo.btTransform(); + frameB.setIdentity(); + frameB.setOrigin(pivotInB); + frameB.setRotation(TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB)); + + this._constraint = new Ammo.btHingeConstraint(selfBody, targetBody, frameA, frameB, this.useReferenceFrameA); + Ammo.destroy(frameB); + break; + case 'TWO_BODIES_PIVOT': + const axisSelf = TempPhyMath.toBtVec(this.axisSelf, TempPhyMath.tmpVecC); + const axisTarget = TempPhyMath.toBtVec(this.axisTarget, TempPhyMath.tmpVecD); + + this._constraint = new Ammo.btHingeConstraint(selfBody, targetBody, pivotInA, pivotInB, axisSelf, axisTarget); + break; + default: + console.error('Invalid constraint type'); + return; + } + + this._pendingLimits && this.setLimit(...this._pendingLimits) + this._pendingMotorConfig && this.enableAngularMotor(...this._pendingMotorConfig) + } +} diff --git a/packages/physics/constraint/PointToPointConstraint.ts b/packages/physics/constraint/PointToPointConstraint.ts new file mode 100644 index 00000000..0d4ed466 --- /dev/null +++ b/packages/physics/constraint/PointToPointConstraint.ts @@ -0,0 +1,20 @@ +import { Ammo } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 点到点约束 + */ +export class PointToPointConstraint extends ConstraintBase { + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null) { + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf); + + if (targetBody) { + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + this._constraint = new Ammo.btPoint2PointConstraint(selfBody, targetBody, pivotInA, pivotInB); + } else { + this._constraint = new Ammo.btPoint2PointConstraint(selfBody, pivotInA); + } + + } +} diff --git a/packages/physics/constraint/SliderConstraint.ts b/packages/physics/constraint/SliderConstraint.ts new file mode 100644 index 00000000..8f288b98 --- /dev/null +++ b/packages/physics/constraint/SliderConstraint.ts @@ -0,0 +1,138 @@ +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ConstraintBase } from './ConstraintBase'; + +/** + * 滑动关节约束 + */ +export class SliderConstraint extends ConstraintBase { + private _lowerLinLimit: number = -1e30; + private _upperLinLimit: number = 1e30; + private _lowerAngLimit: number = -Math.PI; + private _upperAngLimit: number = Math.PI; + private _poweredLinMotor: boolean = false; + private _maxLinMotorForce: number = 0; + private _targetLinMotorVelocity: number = 0; + + /** + * 是否使用线性参考框架。 + * 默认值 `true` + */ + public useLinearReferenceFrame: boolean = true; + + protected createConstraint(selfBody: Ammo.btRigidBody, targetBody: Ammo.btRigidBody | null): void { + const pivotInA = TempPhyMath.toBtVec(this.pivotSelf); + const rotInA = TempPhyMath.toBtQua(this.rotationSelf); + + const frameInA = Physics.TEMP_TRANSFORM; + frameInA.setIdentity(); + frameInA.setOrigin(pivotInA); + frameInA.setRotation(rotInA); + + if (targetBody) { + const pivotInB = TempPhyMath.toBtVec(this.pivotTarget, TempPhyMath.tmpVecB); + const rotInB = TempPhyMath.toBtQua(this.rotationTarget, TempPhyMath.tmpQuaB); + const frameInB = new Ammo.btTransform(); + frameInB.setIdentity(); + frameInB.setOrigin(pivotInB); + frameInB.setRotation(rotInB); + this._constraint = new Ammo.btSliderConstraint(selfBody, targetBody, frameInA, frameInB, this.useLinearReferenceFrame); + Ammo.destroy(frameInB); + } else { + this._constraint = new Ammo.btSliderConstraint(selfBody, frameInA, this.useLinearReferenceFrame); + } + + this._constraint.setLowerLinLimit(this._lowerLinLimit); + this._constraint.setUpperLinLimit(this._upperLinLimit); + this._constraint.setLowerAngLimit(this._lowerAngLimit); + this._constraint.setUpperAngLimit(this._upperAngLimit); + + this._constraint.setPoweredLinMotor(this._poweredLinMotor); + this._constraint.setMaxLinMotorForce(this._maxLinMotorForce); + this._constraint.setTargetLinMotorVelocity(this._targetLinMotorVelocity); + } + + /** + * 线性运动的下限限制。 + * 默认值 `-1e30` 表示无限制 + */ + public get lowerLinLimit(): number { + return this._lowerLinLimit; + } + public set lowerLinLimit(value: number) { + this._lowerLinLimit = value; + this._constraint?.setLowerLinLimit(value); + } + + /** + * 线性运动的上限限制。 + * 默认值 `1e30` 表示无限制 + */ + public get upperLinLimit(): number { + return this._upperLinLimit; + } + public set upperLinLimit(value: number) { + this._upperLinLimit = value; + this._constraint?.setUpperLinLimit(value); + } + + /** + * 角度运动的下限限制。 + * 默认值 `-Math.PI` + */ + public get lowerAngLimit(): number { + return this._lowerAngLimit; + } + public set lowerAngLimit(value: number) { + this._lowerAngLimit = value; + this._constraint?.setLowerAngLimit(value); + } + + /** + * 角度运动的上限限制。 + * 默认值 `Math.PI` + */ + public get upperAngLimit(): number { + return this._upperAngLimit; + } + public set upperAngLimit(value: number) { + this._upperAngLimit = value; + this._constraint?.setUpperAngLimit(value); + } + + /** + * 是否启用线性马达。 + * 默认值 `false` + */ + public get poweredLinMotor(): boolean { + return this._poweredLinMotor; + } + public set poweredLinMotor(value: boolean) { + this._poweredLinMotor = value; + this._constraint?.setPoweredLinMotor(value) + } + + /** + * 线性马达的最大推力。 + * 默认值 `0` + */ + public get maxLinMotorForce(): number { + return this._maxLinMotorForce; + } + public set maxLinMotorForce(value: number) { + this._maxLinMotorForce = value; + this._constraint?.setMaxLinMotorForce(value) + } + + /** + * 线性马达的目标速度。 + * 默认值 `0` + */ + public get targetLinMotorVelocity(): number { + return this._targetLinMotorVelocity; + } + public set targetLinMotorVelocity(value: number) { + this._targetLinMotorVelocity = value; + this._constraint?.setTargetLinMotorVelocity(value) + } +} diff --git a/packages/physics/index.ts b/packages/physics/index.ts index 190efb56..6c0793b2 100644 --- a/packages/physics/index.ts +++ b/packages/physics/index.ts @@ -1,4 +1,18 @@ -export * from './Physics' -export * from './Rigidbody' -export * from './ClothSoftBody' -export * from './HingeConstraint' \ No newline at end of file +export * from './utils/CollisionShapeUtil'; +export * from './utils/ContactProcessedUtil'; +export * from './utils/RigidBodyUtil'; +export * from './utils/RigidBodyMapping'; +export * from './utils/TempPhyMath'; +export * from './Physics'; +export * from './visualDebug/DebugDrawModeEnum'; +export * from './rigidbody/RigidbodyEnum'; +export * from './rigidbody/Rigidbody'; +export * from './rigidbody/GhostTrigger'; +export * from './softbody/ClothSoftbody'; +export * from './constraint/ConeTwistConstraint'; +export * from './constraint/FixedConstraint'; +export * from './constraint/Generic6DofConstraint'; +export * from './constraint/Generic6DofSpringConstraint'; +export * from './constraint/HingeConstraint'; +export * from './constraint/PointToPointConstraint'; +export * from './constraint/SliderConstraint'; \ No newline at end of file diff --git a/packages/physics/package.json b/packages/physics/package.json index e9ff90bf..a270ddc0 100644 --- a/packages/physics/package.json +++ b/packages/physics/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/physics", - "version": "0.2.2", + "version": "0.3.0", "author": "Orillusion", "description": "Orillusion Physics Plugin, Powerd by Ammo.js", "main": "./dist/physics.umd.js", @@ -11,7 +11,7 @@ "scripts": { "build": "vite build && npm run build:types && npm run build:clean", "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", - "build:clean": "mv dist/packages/physics/* dist && rm -rf dist/src & rm -rf dist/packages", + "build:clean": "mv dist/packages/physics/* dist && rm -rf dist/src && rm -rf dist/packages", "docs": "npm run docs:typedoc ../../docs/physics index.ts", "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" }, @@ -21,7 +21,7 @@ "url": "git+https://github.com/Orillusion/orillusion.git" }, "dependencies": { - "@orillusion/ammo": "^0.2.0", - "@orillusion/core": "^0.7.0" + "@orillusion/ammo": "^0.2.1", + "@orillusion/core": "^0.8.0" } } diff --git a/packages/physics/rigidbody/GhostTrigger.ts b/packages/physics/rigidbody/GhostTrigger.ts new file mode 100644 index 00000000..43797101 --- /dev/null +++ b/packages/physics/rigidbody/GhostTrigger.ts @@ -0,0 +1,178 @@ +import { ComponentBase, Vector3 } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { CollisionEventHandler } from './RigidbodyExpansion' +import { CollisionFlags } from './RigidbodyEnum'; + +/** + * The GhostTrigger Component represents a non-physical trigger in the physics world. + * It uses a ghost object to detect overlapping collisions without producing physical responses. + */ +export class GhostTrigger extends ComponentBase { + private _initResolve!: () => void; + private _initializationPromise: Promise = new Promise(r => this._initResolve = r); + private _ghostObject: Ammo.btPairCachingGhostObject; + private _userIndex: number; + private _shape: Ammo.btCollisionShape; + private collisionEventHandler: CollisionEventHandler = new CollisionEventHandler(); + + public get shape() { + return this._shape + } + public set shape(value: Ammo.btCollisionShape) { + this._shape = value; + if (this._ghostObject) { + Ammo.destroy(this._ghostObject.getCollisionShape()); + this._ghostObject.setCollisionShape(value); + // Physics.world.updateSingleAabb(this._ghostObject); // 更新世界中的碰撞体状态 + } + } + + public get userIndex() { + return this._userIndex; + } + + public set userIndex(value: number) { + this._userIndex = value; + this._ghostObject?.setUserIndex(value) + } + + private _collisionFlags: CollisionFlags = CollisionFlags.NO_CONTACT_RESPONSE; + + /** + * 获取碰撞标志 + */ + public get collisionFlags(): number { + return this._ghostObject?.getCollisionFlags() ?? this._collisionFlags; + } + + /** + * 添加单个碰撞标志 + */ + public addCollisionFlag(value: CollisionFlags) { + this._collisionFlags = this.collisionFlags | value; + this._ghostObject?.setCollisionFlags(this._collisionFlags); + } + /** + * 删除单个碰撞标志 + */ + public removeCollisionFlag(value: CollisionFlags) { + this._collisionFlags = this.collisionFlags & ~value; + this._ghostObject?.setCollisionFlags(this._collisionFlags); + } + + async start() { + + if (!this._shape) throw new Error('Ghost object need collision shape') + + let position = this.object3D.localPosition; + let rotation = this.object3D.localRotation; + + this._ghostObject = GhostTrigger.createAndAddGhostObject(this._shape, position, rotation, this._collisionFlags, this._userIndex); + + + // 变换更新,确保三维对象更新变换时同步到幽灵对象 + this.transform.onPositionChange = (oldValue: Vector3, newValue: Vector3) => { + newValue ||= this.transform.localPosition; + this._ghostObject.getWorldTransform().setOrigin(TempPhyMath.toBtVec(newValue)) + }; + this.transform.onRotationChange = (oldValue: Vector3, newValue: Vector3) => { + newValue ||= this.transform.localRotation; + this._ghostObject.getWorldTransform().setRotation(TempPhyMath.eulerToBtQua(newValue)) + }; + this.transform.onScaleChange = (oldValue: Vector3, newValue: Vector3) => { + newValue ||= this.transform.localScale; + this._shape.setLocalScaling(TempPhyMath.toBtVec(newValue)) + }; + + this._initResolve(); + + this.collisionEventHandler.configure(Ammo.getPointer(this._ghostObject)); + + } + + /** + * 创建幽灵对象并添加到物理世界。 + * @param shape - 碰撞形状。 + * @param position - 幽灵对象的位置。 + * @param rotation - 幽灵对象的旋转。 + * @param collisionFlags - 可选参数,碰撞标志,默认值为 4 `NO_CONTACT_RESPONSE` 表示对象不参与碰撞响应,但仍会触发碰撞事件。 + * @param userIndex - 可选参数,用户索引,可作为物理对象标识。 + * @returns 新创建的 Ammo.btPairCachingGhostObject 对象。 + */ + public static createAndAddGhostObject(shape: Ammo.btCollisionShape, position: Vector3, rotation: Vector3, collisionFlags?: number, userIndex?: number) { + let ghostObject = new Ammo.btPairCachingGhostObject(); + let transform = Physics.TEMP_TRANSFORM; + transform.setIdentity(); + transform.setOrigin(TempPhyMath.toBtVec(position)); + transform.setRotation(TempPhyMath.eulerToBtQua(rotation)); + ghostObject.setWorldTransform(transform); + + // 设置形状和属性 + ghostObject.setCollisionShape(shape); + collisionFlags ??= CollisionFlags.NO_CONTACT_RESPONSE; + ghostObject.setCollisionFlags(ghostObject.getCollisionFlags() | collisionFlags); + + if (userIndex != null) { + ghostObject.setUserIndex(userIndex); + } + + // 将 Ghost Object 添加到物理世界 + Physics.world.addCollisionObject(ghostObject); + + return ghostObject; + } + + /** + * 获取幽灵对象 + */ + public get ghostObject() { + return this._ghostObject; + } + + /** + * 异步获取完成初始化的幽灵对象 + */ + public async wait() { + await this._initializationPromise; + return this._ghostObject!; + } + + /** + * 启用/禁用碰撞回调 + */ + public get enableCollisionEvent(): boolean { + return this.collisionEventHandler.enableCollisionEvent; + } + public set enableCollisionEvent(value: boolean) { + this.collisionEventHandler.enableCollisionEvent = value; + + } + + /** + * 碰撞事件回调 + */ + public get collisionEvent() { + return this.collisionEventHandler.collisionEvent; + } + public set collisionEvent(callback: (contactPoint: Ammo.btManifoldPoint, selfBody: Ammo.btRigidBody, otherBody: Ammo.btRigidBody) => void) { + this.collisionEventHandler.collisionEvent = callback; + } + + public destroy(force?: boolean): void { + if (this._ghostObject) { + Physics.world.removeCollisionObject(this._ghostObject); + Ammo.destroy(this._ghostObject.getCollisionShape()); + Ammo.destroy(this._ghostObject); + this._ghostObject = null; + } + + this._shape = null; + this.transform.onPositionChange = null; + this.transform.onRotationChange = null; + this.transform.onScaleChange = null; + this.collisionEventHandler.destroy(); + + super.destroy(force); + } +} diff --git a/packages/physics/rigidbody/Rigidbody.ts b/packages/physics/rigidbody/Rigidbody.ts new file mode 100644 index 00000000..7934806b --- /dev/null +++ b/packages/physics/rigidbody/Rigidbody.ts @@ -0,0 +1,598 @@ +import { Vector3, BoxColliderShape, CapsuleColliderShape, ColliderComponent, ComponentBase, Quaternion, SphereColliderShape } from '@orillusion/core' +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { ActivationState, CollisionFlags } from './RigidbodyEnum'; +import { PhysicsTransformSync, CollisionEventHandler } from './RigidbodyExpansion' +import { CollisionShapeUtil } from '../utils/CollisionShapeUtil'; +import { ContactProcessedUtil } from '../utils/ContactProcessedUtil'; +import { RigidBodyUtil } from '../utils/RigidBodyUtil'; + +/** + * Rigidbody Component + * Rigid bodies can endow game objects with physical properties, allowing them to be controlled by the physics system and subjected to forces and torques, thus achieving realistic motion effects. + * @group Components + */ +export class Rigidbody extends ComponentBase { + private _initResolve!: () => void; + private _initializationPromise: Promise = new Promise(r => this._initResolve = r); + private _btBodyInited: boolean = false; + private _btRigidbody: Ammo.btRigidBody; + private _shape: Ammo.btCollisionShape; + private _mass: number = 0.01; + private _margin: number = 0.02; + private _velocity: Vector3 = new Vector3(); + private _angularVelocity: Vector3 = new Vector3(); + private _linearVelocity: Vector3 = new Vector3(); + private _gravity: Vector3 = Physics.gravity.clone(); + private _restitution: number = 0.5; // 低恢复系数以减少弹跳 + private _friction: number = 0.5; // 高摩擦系数以防止滑动 + private _rollingFriction: number; + private _contactProcessingThreshold: number; // 接触处理阈值 值越小,精度越高 + private _damping: [number, number]; + private _ccdSettings: [number, number]; + private _activationState: ActivationState; + private _collisionFlags: CollisionFlags; // Default static: 1, dynamic: 0 + private _userIndex: number; + private _isSilent: boolean = false; + + private collisionEventHandler: CollisionEventHandler; + private physicsTransformSync: PhysicsTransformSync; + public static readonly collisionShape = CollisionShapeUtil; + + init() { + this.physicsTransformSync = new PhysicsTransformSync(this.transform); + this.collisionEventHandler = new CollisionEventHandler(); + } + + public start(): void { + + this.initRigidbody(); + + this.physicsTransformSync.configure(this._btRigidbody, this._mass); + this.collisionEventHandler.configure(this._btRigidbody.kB); + + this._isSilent && ContactProcessedUtil.addIgnoredPointer(this._btRigidbody.kB); + + this._btBodyInited = true; + this._initResolve(); + } + + private initRigidbody(): void { + // 如果未传入形状则应用碰撞组件的形状与参数构建碰撞体 + if (!this._shape) this._shape = this.createColliderComponentShape(); + + let position: Vector3 = this.object3D.localPosition; + + // 处理特殊形状 高度场地形 + if (this._shape instanceof Ammo.btHeightfieldTerrainShape) { + // averageHeight 是碰撞体对象中自定义的属性,应用该值以调整刚体的位置 + position = position.clone(); + position.y += (this._shape as any)?.averageHeight || 0; + } + + this._shape.setMargin(this._margin); + + this._btRigidbody = RigidBodyUtil.createRigidBody(this.object3D, this._shape, this._mass, position); + + // 刚体配置信息 + this._btRigidbody.setRestitution(this._restitution); + this._btRigidbody.setFriction(this._friction); + + if (this._rollingFriction != null) { + this._btRigidbody.setRollingFriction(this._rollingFriction); + } + + if (this._damping != null) { + this._btRigidbody.setDamping(...this._damping); + } + + if (this._userIndex != null) { + this._btRigidbody.setUserIndex(this._userIndex); + } + + if (this._activationState != null) { + this._btRigidbody.setActivationState(this._activationState); + } + + if (this._contactProcessingThreshold != null) { + this._btRigidbody.setContactProcessingThreshold(this._contactProcessingThreshold); + } + + if (this._collisionFlags != null) { + this._btRigidbody.setCollisionFlags(this._collisionFlags); + } + + if (this.group != null && this.mask != null) { + Physics.world.addRigidBody(this._btRigidbody, this.group, this.mask); + } else { + Physics.world.addRigidBody(this._btRigidbody); + } + + // The gravity setting is done after the rigid body is added to the physical world. + if (!this._gravity.equals(Physics.gravity)) { + this._btRigidbody.setGravity(TempPhyMath.toBtVec(this._gravity)); + } + + // Continuous Collision Detection + if (this._ccdSettings != null) { + this._btRigidbody.setCcdMotionThreshold(this._ccdSettings[0]); + this._btRigidbody.setCcdSweptSphereRadius(this._ccdSettings[1]); + } + } + + public onUpdate(): void { + // Check if the rigid body is active in the physics simulation. + if (this._btRigidbody?.isActive()) { + // Retrieve the current interpolated world transform of the rigid body from its motion state. + // The motion state provides an interpolated transformation, which is smoother and more suitable + this._btRigidbody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + let pos = Physics.TEMP_TRANSFORM.getOrigin(); + let qua = Physics.TEMP_TRANSFORM.getRotation(); + + // When physicsTransformSync is enabled, setting isUpdatingFromPhysics to true + // prevents the 3D object's onChange event from reacting to this automatic update. + // This is used to distinguish between automatic synchronization and manual transformations. + this.physicsTransformSync.isUpdatingFromPhysics = true; + + // Synchronize the position and rotation of the 3D object with the rigid body's transform. + this.transform.localPosition = Vector3.HELP_0.set(pos.x(), pos.y(), pos.z()); + this.transform.localRotQuat = Quaternion.HELP_0.set(qua.x(), qua.y(), qua.z(), qua.w()); + + // Re-enable onChange event handling for manual transformations. + this.physicsTransformSync.isUpdatingFromPhysics = false; + + Physics.checkBound(this); + } + } + + private createColliderComponentShape(): Ammo.btCollisionShape { + let collider = this.object3D.getComponent(ColliderComponent); + if (!collider) throw new Error("Rigid bodies need collision shape"); + + let colliderShape = collider.shape; + let shape: Ammo.btCollisionShape; + if (colliderShape instanceof BoxColliderShape) { + shape = new Ammo.btBoxShape(TempPhyMath.toBtVec(colliderShape.halfSize)); + } else if (colliderShape instanceof CapsuleColliderShape) { + shape = new Ammo.btCapsuleShape(colliderShape.radius, colliderShape.height); + } else if (colliderShape instanceof SphereColliderShape) { + shape = new Ammo.btSphereShape(colliderShape.radius); + } else { + throw new Error("Wrong collision shape"); + } + + return shape; + } + + /** + * 更新刚体的位置和旋转,并同步三维对象 + * @param position 可选,默认为三维对象的位置 + * @param rotation 可选,默认为三维对象的欧拉角旋转 + * @param clearFV 可选,清除刚体的力和速度,默认为 false + */ + public updateTransform(position?: Vector3, rotation?: Vector3 | Quaternion, clearFV?: boolean): void { + if (!this._btRigidbody) return; + position ||= this.transform.localPosition; + rotation ||= this.transform.localRotation; + RigidBodyUtil.updateTransform(this._btRigidbody, position, rotation, clearFV); + Physics.syncGraphic(this.object3D, this._btRigidbody.getWorldTransform()); + } + + /** + * Remove the force and velocity of the rigid body + */ + public clearForcesAndVelocities() { + if (this._btRigidbody) { + RigidBodyUtil.clearForcesAndVelocities(this._btRigidbody); + } + } + + /** + * Check if rigidbody inited + */ + public get btBodyInited(): boolean { + return this._btBodyInited; + } + + /** + * Return internal Ammo.btRigidBody + */ + public get btRigidbody(): Ammo.btRigidBody { + return this._btRigidbody; + } + /** + * Asynchronously retrieves the fully initialized rigid body instance. + */ + public async wait(): Promise { + await this._initializationPromise; + return this._btRigidbody!; + } + + /** + * The collision shape of the rigid body. + */ + public get shape() { + return this._shape; + } + public set shape(value: Ammo.btCollisionShape) { + this._shape = value; + if (this._btRigidbody) { + Ammo.destroy(this._btRigidbody.getCollisionShape()); + this._btRigidbody.setCollisionShape(value); + // Physics.world.updateSingleAabb(this._btRigidbody); // 更新世界中的碰撞体状态 + // 对于高度场形状,需要调整其刚体的位置以匹配三维对象 + if (value instanceof Ammo.btHeightfieldTerrainShape) { + this._btRigidbody.getWorldTransform().getOrigin().setY((value as any).averageHeight + this.object3D.y) + } + } + } + + /** + * The collision group of the rigid body. + */ + public group: number; + + /** + * The collision mask of the rigid body. + */ + public mask: number; + + /** + * User index, which can be used as an identifier for the rigid body. + */ + public get userIndex() { + return this._userIndex + } + + /** + * Sets the user index for the rigid body. + */ + public set userIndex(value: number) { + this._userIndex = value; + this._btRigidbody?.setUserIndex(value); + } + + /** + * Activation state of the rigid body. + */ + public get activationState() { + return this._activationState; + } + + /** + * Sets the activation state of the rigid body. + */ + public set activationState(value: ActivationState) { + this._activationState = value; + this._btRigidbody?.setActivationState(value); + } + + /** + * Collision flags of the rigid body. + */ + public get collisionFlags(): number { + return this._btRigidbody?.getCollisionFlags() ?? (this.mass === 0 ? 1 : 0); + } + + /** + * Adds a collision flag to the rigid body. + */ + public addCollisionFlag(value: CollisionFlags) { + this._collisionFlags = this.collisionFlags | value; + this._btRigidbody?.setCollisionFlags(this._collisionFlags); + } + /** + * Removes a collision flag from the rigid body. + */ + public removeCollisionFlag(value: CollisionFlags) { + this._collisionFlags = this.collisionFlags & ~value; + this._btRigidbody?.setCollisionFlags(this._collisionFlags); + } + + /** + * Check if the rigidbody affect physics system + */ + public get isKinematic(): boolean { + return Boolean(this._btRigidbody?.isKinematicObject()); + } + /** + * Set the rigid body to a kinematic object + */ + public set isKinematic(value: boolean) { + if (value === this.isKinematic) return; + let flag = CollisionFlags.KINEMATIC_OBJECT; + value ? this.addCollisionFlag(flag) : this.removeCollisionFlag(flag); + if (!this._btRigidbody) return; + this.enablePhysicsTransformSync = value; + + if (value) { + // pause onUpdate + this.enable = false + this._btRigidbody.setActivationState(ActivationState.DISABLE_DEACTIVATION) + // sync transfrom + this.updateTransform(); + } else { + // resume onUpdate + this.enable = true + const state = this._activationState ?? ((this._btRigidbody.isStaticObject() + ? ActivationState.ISLAND_SLEEPING + : ActivationState.ACTIVE_TAG)); + this._btRigidbody.forceActivationState(state); + this._btRigidbody.activate() + } + } + /** + * Check if the rigid body is a trigger + */ + public get isTrigger(): boolean { + return (this.collisionFlags & CollisionFlags.NO_CONTACT_RESPONSE) !== 0; + } + /** + * Set the rigid body as a trigger + */ + public set isTrigger(value: boolean) { + let flag = CollisionFlags.NO_CONTACT_RESPONSE; + value ? this.addCollisionFlag(flag) : this.removeCollisionFlag(flag); + } + /** + * Check if the rigid body is visible in debug mode + */ + public get isDisableDebugVisible(): boolean { + return (this.collisionFlags & CollisionFlags.DISABLE_VISUALIZE_OBJECT) !== 0; + } + /** + * Set the rigid body to be visible in debug mode + */ + public set isDisableDebugVisible(value: boolean) { + let flag = CollisionFlags.DISABLE_VISUALIZE_OBJECT; + value ? this.addCollisionFlag(flag) : this.removeCollisionFlag(flag); + } + /** + * Margin of the collision shape. + */ + public get margin() { + return this._margin; + } + /** + * Sets the margin of the collision shape. + * @default 0.02 + */ + public set margin(value: number) { + this._margin = value; + this._shape?.setMargin(value) + } + + /** + * Damping of the rigid body. + * + * Sets the damping parameters. The first value is the linear damping, the second is the angular damping. + * @param params - [linear damping, angular damping] + */ + public get damping(): [number, number] { + return this._damping; + } + + public set damping(params: [number, number]) { + this._damping = [params[0], params[1]]; + this._btRigidbody?.setDamping(...params); + } + /** + * Contact processing threshold of the rigid body. + */ + public get contactProcessingThreshold() { + return this._contactProcessingThreshold + } + /** + * Sets the contact processing threshold of the rigid body. + */ + public set contactProcessingThreshold(value: number) { + this._contactProcessingThreshold = value; + this._btRigidbody?.setContactProcessingThreshold(value) + } + /** + * Gravity vector applied to the rigid body. + */ + public get gravity() { + return this._gravity + } + /** + * Sets the gravity vector applied to the rigid body. + */ + public set gravity(value: Vector3) { + this._gravity.copyFrom(value); + this._btRigidbody?.setGravity(TempPhyMath.toBtVec(value)); + } + /** + * Get friction value + */ + public get friction() { + return this._friction; + } + /** + * Set friction value. default `0.5` + */ + public set friction(value: number) { + this._friction = value; + this._btRigidbody?.setFriction(value); + } + /** + * Get rolling friction value + */ + public get rollingFriction(): number { + return this._rollingFriction; + } + /** + * Set rolling friction value + */ + public set rollingFriction(value: number) { + this._rollingFriction = value; + this._btRigidbody?.setRollingFriction(value); + } + /** + * Get restitution value + */ + public get restitution(): number { + return this._restitution; + } + /** + * Set restitution value default `0.5` + */ + public set restitution(value: number) { + this._restitution = value; + this._btRigidbody?.setRestitution(value); + } + /** + * Get velocity value of current object + */ + public get velocity(): Vector3 { + return this._velocity; + } + /** + * Set velocity value of current object + */ + public set velocity(value: Vector3) { + this._velocity.copyFrom(value); + this._btRigidbody?.applyForce(TempPhyMath.toBtVec(value), TempPhyMath.zeroBtVec(TempPhyMath.tmpVecB)); + } + + /** + * Get the angular velocity value of current object + */ + public get angularVelocity(): Vector3 { + if (this._btBodyInited) { + return TempPhyMath.fromBtVec(this._btRigidbody.getAngularVelocity(), this._angularVelocity); + } + return this._angularVelocity; + } + /** + * Set the angular velocity value of current object + */ + public set angularVelocity(value: Vector3) { + this._btRigidbody?.setAngularVelocity(TempPhyMath.toBtVec(value)) + } + /** + * Get the linear velocity value of current object + */ + public get linearVelocity(): Vector3 { + if (this._btBodyInited) { + return TempPhyMath.fromBtVec(this._btRigidbody.getLinearVelocity(), this._linearVelocity); + } + return this._linearVelocity; + } + /** + * Set the linear velocity value of current object + */ + public set linearVelocity(value: Vector3) { + this._btRigidbody?.setLinearVelocity(TempPhyMath.toBtVec(value)) + } + /** + * Get mass value + */ + public get mass(): number { + return this._mass; + } + /** + * Set mass value. default `0.01` + */ + public set mass(value: number) { + const oldMass = this._mass; + this._mass = value; + if (this._btRigidbody && oldMass !== value) { + if (oldMass === 0 || value === 0) { + ContactProcessedUtil.removeIgnoredPointer(this._btRigidbody.kB); // 指针将会无效,从静默状态表中移除 + Physics.world.removeRigidBody(this._btRigidbody); // 删除刚体 + this.initRigidbody(); // 重新创建刚体 + this.collisionEventHandler.configure(this._btRigidbody.kB); + this._isSilent && ContactProcessedUtil.addIgnoredPointer(this._btRigidbody.kB); + + } else { + // 根据碰撞形状计算新的惯性进行更新 + const localInertia = TempPhyMath.zeroBtVec(); + this._btRigidbody.getCollisionShape().calculateLocalInertia(value, localInertia); + this._btRigidbody.setMassProps(value, localInertia); + this._btRigidbody.updateInertiaTensor(); + this.clearForcesAndVelocities(); + } + this.physicsTransformSync.configure(this._btRigidbody, value); + } + } + + /** + * 刚体的静默状态。 + * 如果为 true 则任何物理对象与静默状态的对象发生碰撞时都不会触发双方的碰撞回调。 + */ + public get isSilent(): boolean { + return this._isSilent; + } + public set isSilent(value: boolean) { + this._isSilent = value; + if (value) { + ContactProcessedUtil.addIgnoredPointer(this._btRigidbody?.kB) + } else { + ContactProcessedUtil.removeIgnoredPointer(this._btRigidbody?.kB) + } + } + + /** + * CCD (Continuous Collision Detection) + * + * Sets the CCD parameters. The first value is the motion threshold, the second is the swept sphere radius. + * @param params - [motion threshold, swept sphere radius] + */ + public set ccdSettings(params: [number, number]) { + this._ccdSettings = [params[0], params[1]]; + this._btRigidbody?.setCcdMotionThreshold(params[0]); + this._btRigidbody?.setCcdSweptSphereRadius(params[1]); + } + + public get ccdSettings(): [number, number] { + return this._ccdSettings; + } + + /** + * Enable/disable collision callbacks + */ + public get enableCollisionEvent(): boolean { + return this.collisionEventHandler.enableCollisionEvent; + } + public set enableCollisionEvent(value: boolean) { + this.collisionEventHandler.enableCollisionEvent = value; + + } + + /** + * Collision callbacks + */ + public get collisionEvent() { + return this.collisionEventHandler.collisionEvent; + } + public set collisionEvent(callback: (contactPoint: Ammo.btManifoldPoint, selfBody: Ammo.btRigidBody, otherBody: Ammo.btRigidBody) => void) { + this.collisionEventHandler.collisionEvent = callback; + } + + /** + * Enables or disables the transform sync with physics. + * If enabled, changes to the transform will automatically update the physics body. + */ + public get enablePhysicsTransformSync() { + return this.physicsTransformSync.enablePhysicsTransformSync; + } + public set enablePhysicsTransformSync(value: boolean) { + if (this.isDestroyed) return; + this.physicsTransformSync.enablePhysicsTransformSync = value; + } + + public destroy(force?: boolean): void { + if (this._btRigidbody) { + ContactProcessedUtil.removeIgnoredPointer(this._btRigidbody.kB); + RigidBodyUtil.destroyRigidBody(this._btRigidbody) + } + this._btRigidbody = null; + this._btBodyInited = false; + this._shape = null; + + this.physicsTransformSync.destroy() + this.collisionEventHandler.destroy() + super.destroy(force); + } +} diff --git a/packages/physics/rigidbody/RigidbodyEnum.ts b/packages/physics/rigidbody/RigidbodyEnum.ts new file mode 100644 index 00000000..cdbe6800 --- /dev/null +++ b/packages/physics/rigidbody/RigidbodyEnum.ts @@ -0,0 +1,79 @@ +/** + * Collision flags + */ +export enum CollisionFlags { + /** + * Default flag for dynamic rigid bodies. + */ + DEFAULT = 0, + /** + * Used for static objects. These objects do not move but can be collided with by other objects. + */ + STATIC_OBJECT = 1, + /** + * Used for kinematic objects. These objects are not affected by physical forces (like gravity or collisions) but can be moved programmatically and affect dynamic objects they collide with. + */ + KINEMATIC_OBJECT = 2, + /** + * Objects with this flag do not participate in collision response but still trigger collision events. + */ + NO_CONTACT_RESPONSE = 4, + /** + * This flag indicates that the object will use a custom material interaction callback. + */ + CUSTOM_MATERIAL_CALLBACK = 8, + /** + * Special flag for collision objects used by character controllers. This is typically used to optimize character collision handling in games. + */ + CHARACTER_OBJECT = 16, + /** + * Prevents this object from being displayed in the physical debug view. + */ + DISABLE_VISUALIZE_OBJECT = 32, + /** + * Prevents this object’s collision from being processed on the auxiliary processing unit, optimizing performance on specific hardware platforms. + */ + DISABLE_SPU_COLLISION_PROCESSING = 64, + /** + * Enables custom contact stiffness and damping settings for this object. This allows adjusting the physical response's stiffness and damping when handling collisions, used to simulate more complex physical interactions. + */ + HAS_CONTACT_STIFFNESS_DAMPING = 128, + /** + * Allows specifying a custom rendering color for this object in the physical debug view. This helps differentiate and identify specific physical objects during debugging. + */ + HAS_CUSTOM_DEBUG_RENDERING_COLOR = 256, + /** + * Enables friction anchors for this object. Friction anchors improve the friction effect on contact surfaces, typically used for vehicle tires to enhance grip on the ground and reduce sliding. + */ + HAS_FRICTION_ANCHOR = 512, + /** + * Triggers sound effects when this object collides. This flag can be used to configure sound feedback for specific collisions, enhancing the realism and immersion of the game or simulation environment. + */ + HAS_COLLISION_SOUND_TRIGGER = 1024, +} + +/** + * Activation states + */ +export enum ActivationState { + /** + * The object is active and will be processed by the simulation. + */ + ACTIVE_TAG = 1, + /** + * The object is inactive but may be activated if other active objects collide with it. + */ + ISLAND_SLEEPING = 2, + /** + * The object is requesting to be deactivated in the next simulation step. If there is no further interaction, the object will enter a sleeping state. + */ + WANTS_DEACTIVATION = 3, + /** + * Disables automatic sleeping. The object will continue to be simulated even if it is stationary. + */ + DISABLE_DEACTIVATION = 4, + /** + * The object will not be simulated by the physics engine, whether dynamic or colliding, but can be moved or manipulated programmatically. + */ + DISABLE_SIMULATION = 5, +} diff --git a/packages/physics/rigidbody/RigidbodyExpansion.ts b/packages/physics/rigidbody/RigidbodyExpansion.ts new file mode 100644 index 00000000..588b42c2 --- /dev/null +++ b/packages/physics/rigidbody/RigidbodyExpansion.ts @@ -0,0 +1,132 @@ +import { Vector3, Transform } from '@orillusion/core' +import { Ammo, Physics } from '../Physics' +import { RigidBodyUtil } from '../utils/RigidBodyUtil'; +import { ContactProcessedUtil } from '../utils/ContactProcessedUtil'; + + +/** + * @class PhysicsTransformSync + * @description This class manages the synchronization between the physics engine's rigid body and the 3D object's transform. + * It allows enabling or disabling the automatic update of the physics body when the transform changes. + */ +export class PhysicsTransformSync { + public isUpdatingFromPhysics: boolean = false; + private _btRigidbody: Ammo.btRigidBody; + private _mass: number; + + private _enablePhysicsTransformSync: boolean = false; + private transform: Transform; + + constructor(transform: Transform) { + this.transform = transform; + } + + public configure(body: Ammo.btRigidBody, mass: number) { + this._btRigidbody = body; + this._mass = mass; + } + + /** + * Enables or disables the transform sync with physics. + * If enabled, changes to the transform will automatically update the physics body. + */ + public set enablePhysicsTransformSync(value: boolean) { + if (this._enablePhysicsTransformSync === value) return; + this._enablePhysicsTransformSync = value; + this.isUpdatingFromPhysics = !value; + + this.transform.onPositionChange = value ? this.onPositionChange.bind(this) : null; + this.transform.onRotationChange = value ? this.onRotationChange.bind(this) : null; + this.transform.onScaleChange = value ? this.onScaleChange.bind(this) : null; + + if (value && this._btRigidbody) { + RigidBodyUtil.updateTransform(this._btRigidbody, this.transform.localPosition, this.transform.localRotation, false); + Physics.syncGraphic(this.transform.object3D, this._btRigidbody.getWorldTransform()); + this.onScaleChange(); + } + } + + public get enablePhysicsTransformSync() { + return this._enablePhysicsTransformSync; + } + + private onPositionChange(oldValue?: Vector3, newValue?: Vector3) { + if (this.isUpdatingFromPhysics) return; + newValue ||= this.transform.localPosition; + RigidBodyUtil.updatePosition(this._btRigidbody, newValue); + }; + + private onRotationChange(oldValue?: Vector3, newValue?: Vector3) { + if (this.isUpdatingFromPhysics) return; + newValue ||= this.transform.localRotation; + RigidBodyUtil.updateRotation(this._btRigidbody, newValue); + } + + private onScaleChange(oldValue?: Vector3, newValue?: Vector3) { + newValue ||= this.transform.localScale; + RigidBodyUtil.updateScale(this._btRigidbody, newValue, this._mass); + } + + public destroy() { + this._btRigidbody = null; + if (this._enablePhysicsTransformSync) { + this.transform.onPositionChange = null; + this.transform.onRotationChange = null; + this.transform.onScaleChange = null; + } + this.transform = null; + } +} + + +/** + * @class CollisionEventHandler + * @description This class handles the registration and configuration of collision events for physics bodies. + * It allows enabling or disabling collision events and setting custom collision callbacks. + */ +export class CollisionEventHandler { + private _pointer: number; + private _collisionEvent: (contactPoint: Ammo.btManifoldPoint, selfBody: Ammo.btRigidBody, otherBody: Ammo.btRigidBody) => void; + private _enableCollisionEvent: boolean = true; + + public configure(pointer: number) { + this._pointer && ContactProcessedUtil.unregisterCollisionCallback(this._pointer); + this._pointer = pointer; + this.configureCollisionEvent(); + } + + public get enableCollisionEvent() { + return this._enableCollisionEvent; + } + + public set enableCollisionEvent(value: boolean) { + if (this._enableCollisionEvent !== value) { + this._enableCollisionEvent = value; + this.configureCollisionEvent(); + } + } + + public get collisionEvent() { + return this._collisionEvent; + } + + public set collisionEvent(callback: (contactPoint: Ammo.btManifoldPoint, selfBody: Ammo.btRigidBody, otherBody: Ammo.btRigidBody) => void) { + this._collisionEvent = callback; + this.configureCollisionEvent() + } + + private configureCollisionEvent() { + if (this._pointer && this._collisionEvent) { + if (this._enableCollisionEvent) { + ContactProcessedUtil.registerCollisionCallback(this._pointer, this.collisionEvent); + } else { + ContactProcessedUtil.unregisterCollisionCallback(this._pointer); + } + } + } + + public destroy() { + ContactProcessedUtil.unregisterCollisionCallback(this._pointer); + this._collisionEvent = null; + } +} \ No newline at end of file diff --git a/packages/physics/softbody/ClothSoftbody.ts b/packages/physics/softbody/ClothSoftbody.ts new file mode 100644 index 00000000..dcf1c13b --- /dev/null +++ b/packages/physics/softbody/ClothSoftbody.ts @@ -0,0 +1,397 @@ +// ClothSoftbody.ts + +import { Vector3, MeshRenderer, PlaneGeometry, ComponentBase, VertexAttributeName, Quaternion } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { Rigidbody } from '../rigidbody/Rigidbody'; + +/** + * 软体布料平面的各个角 + */ +export type CornerType = 'leftTop' | 'rightTop' | 'leftBottom' | 'rightBottom' | 'left' | 'right' | 'top' | 'bottom' | 'center'; + +export class ClothSoftbody extends ComponentBase { + private _initResolve!: () => void; + private _initializationPromise: Promise = new Promise(r => this._initResolve = r); + private _btBodyInited: boolean = false; + private _btSoftbody: Ammo.btSoftBody; // 创建的 Ammo 软体实例 + private _btRigidbody: Ammo.btRigidBody; // 通过锚点附加的 Ammo 刚体实例 + private _anchorRigidbody: Rigidbody; + private _segmentW: number; + private _segmentH: number; + private _geometry: PlaneGeometry; + private _diff: Vector3 = new Vector3(); + + /** + * 布料四个角的位置 (00,01,10,11) + */ + public clothCorners: [Vector3, Vector3, Vector3, Vector3]; + + /** + * 软体的总质量 + * @default 1 + */ + public mass: number = 1; + + /** + * 软体的碰撞边距 + * @default 0.05 + */ + public margin: number = 0.05; + + /** + * 固定布料的节点 + */ + public fixNodeIndices: CornerType[] | number[] = []; + + /** + * 布料的锚点 + */ + public anchorIndices: CornerType[] | number[] = []; + + /** + * 锚定的影响力。影响力值越大,软体节点越紧密地跟随刚体的运动。通常,这个值在0到1之间 + * @default 0.5 + */ + public influence: number | number[] = 0.5; + + /** + * 是否禁用锚定节点与刚体之间的碰撞,将其设置为true可以防止锚定节点和刚体之间发生物理碰撞 + * @default false + */ + public disableCollision: boolean | boolean[] = false; + + /** + * 当没有附加(锚定)到刚体时,应用绝对位置,否则是基于刚体的相对位置 + */ + public applyPosition: Vector3 = new Vector3(); + + /** + * 当没有附加(锚定)到刚体时,应用绝对旋转,否则是基于刚体的相对旋转 + */ + public applyRotation: Vector3 = new Vector3(); + + /** + * 碰撞组 + * @default 1 + */ + public group: number = 1; + + /** + * 碰撞掩码 + * @default -1 + */ + public mask: number = -1; + + /** + * 添加锚点时需要的刚体 + */ + public get anchorRigidbody(): Rigidbody { + return this._anchorRigidbody; + } + + public set anchorRigidbody(value: Rigidbody) { + this._anchorRigidbody = value; + this._diff.set(0, 0, 0); + } + + public get btBodyInited(): boolean { + return this._btBodyInited; + } + + /** + * return the soft body instance + */ + public get btSoftbody(): Ammo.btSoftBody { + return this._btSoftbody; + } + + /** + * Asynchronously retrieves the fully initialized soft body instance. + */ + public async wait(): Promise { + await this._initializationPromise; + return this._btSoftbody; + } + + /** + * 停止软体运动 + */ + public stopSoftBodyMovement(): void { + const nodes = this._btSoftbody.get_m_nodes(); + for (let i = 0; i < nodes.size(); i++) { + const node = nodes.at(i); + node.get_m_v().setValue(0, 0, 0); + node.get_m_f().setValue(0, 0, 0); + } + } + + init(): void { + + if (!Physics.isSoftBodyWord) { + throw new Error('Enable soft body simulation by setting Physics.init({useSoftBody: true}) during initialization.'); + } + + let geometry = this.object3D.getComponent(MeshRenderer).geometry; + if (!(geometry instanceof PlaneGeometry)) throw new Error('The cloth softbody requires plane geometry'); + this._geometry = geometry; + this._segmentW = geometry.segmentW; + this._segmentH = geometry.segmentH; + } + + async start(): Promise { + + if (this._anchorRigidbody) { + this._btRigidbody = await this._anchorRigidbody.wait(); + } + + this.initSoftBody(); + + this._btBodyInited = true; + this._initResolve(); + } + + private initSoftBody(): void { + + // Defines the four corners of the cloth + let clothCorner00: Ammo.btVector3, + clothCorner01: Ammo.btVector3, + clothCorner10: Ammo.btVector3, + clothCorner11: Ammo.btVector3; + + if (!this.clothCorners) { + const halfWidth = this._geometry.width / 2; + const halfHeight = this._geometry.height / 2; + clothCorner00 = TempPhyMath.setBtVec(-halfWidth, halfHeight, 0, TempPhyMath.tmpVecA); + clothCorner01 = TempPhyMath.setBtVec(halfWidth, halfHeight, 0, TempPhyMath.tmpVecB); + clothCorner10 = TempPhyMath.setBtVec(-halfWidth, -halfHeight, 0, TempPhyMath.tmpVecC); + clothCorner11 = TempPhyMath.setBtVec(halfWidth, -halfHeight, 0, TempPhyMath.tmpVecD); + } else { + clothCorner00 = TempPhyMath.toBtVec(this.clothCorners[0], TempPhyMath.tmpVecA) + clothCorner01 = TempPhyMath.toBtVec(this.clothCorners[1], TempPhyMath.tmpVecB); + clothCorner10 = TempPhyMath.toBtVec(this.clothCorners[2], TempPhyMath.tmpVecC); + clothCorner11 = TempPhyMath.toBtVec(this.clothCorners[3], TempPhyMath.tmpVecD); + } + + this._btSoftbody = new Ammo.btSoftBodyHelpers().CreatePatch( + Physics.worldInfo, + clothCorner00, + clothCorner01, + clothCorner10, + clothCorner11, + this._segmentW + 1, + this._segmentH + 1, + 0, + true + ); + + this.configureSoftBody(this._btSoftbody); + + this._btSoftbody.setTotalMass(this.mass, false); + Ammo.castObject(this._btSoftbody, Ammo.btCollisionObject).getCollisionShape().setMargin(this.margin); + this._btSoftbody.generateBendingConstraints(2, this._btSoftbody.get_m_materials().at(0)); + + // 固定节点 + if (this.fixNodeIndices.length > 0) { + this.applyFixedNodes(this.fixNodeIndices); + } + + // 添加锚点 + if (this.anchorIndices.length > 0) { + if (!this._btRigidbody) throw new Error('Needs a rigid body'); + this.setAnchor(); + } else { + // 先旋转再平移,矩阵变换不满足交换律 + this._btSoftbody.rotate(TempPhyMath.eulerToBtQua(this.applyRotation)); + this._btSoftbody.translate(TempPhyMath.toBtVec(this.applyPosition)); + } + + // 布料变换将由顶点更新表示,避免影响需要重置三维对象变换 + this.transform.localPosition = Vector3.ZERO; + this.transform.localRotation = Vector3.ZERO; + + (Physics.world as Ammo.btSoftRigidDynamicsWorld).addSoftBody(this._btSoftbody, this.group, this.mask); + } + + private configureSoftBody(softBody: Ammo.btSoftBody): void { + // 设置配置参数 + let sbConfig = softBody.get_m_cfg(); + sbConfig.set_viterations(10); // 位置迭代次数 + sbConfig.set_piterations(10); // 位置求解器迭代次数 + // sbConfig.set_diterations(10); // 动力学迭代次数 + // sbConfig.set_citerations(10); // 碰撞迭代次数 + // sbConfig.set_kVCF(1.0); // 速度收敛系数 + // sbConfig.set_kDP(0.1); // 阻尼系数 + // sbConfig.set_kDG(0.0); // 阻力系数 + // sbConfig.set_kLF(0.05); // 升力系数 + // sbConfig.set_kPR(0.0); // 压力系数 + // sbConfig.set_kVC(0.0); // 体积保护系数 + // sbConfig.set_kDF(0.0); // 动力学系数 + // sbConfig.set_kMT(0.0); // 电磁系数 + // sbConfig.set_kCHR(1.0); // 刚性系数 + // sbConfig.set_kKHR(0.5); // 刚性恢复系数 + // sbConfig.set_kSHR(1.0); // 剪切刚性系数 + // sbConfig.set_kAHR(0.1); // 角度恢复系数 + // sbConfig.set_kSRHR_CL(1.0); // 拉伸刚性恢复系数 + // sbConfig.set_kSKHR_CL(0.5); // 刚性恢复系数 + // sbConfig.set_kSSHR_CL(0.1); // 剪切刚性恢复系数 + // sbConfig.set_kSR_SPLT_CL(0.5); // 拉伸分割系数 + // sbConfig.set_kSK_SPLT_CL(0.5); // 剪切分割系数 + // sbConfig.set_kSS_SPLT_CL(0.5); // 剪切分割系数 + // sbConfig.set_maxvolume(1.0); // 最大体积 + // sbConfig.set_timescale(1.0); // 时间缩放系数 + // sbConfig.set_collisions(0); // 碰撞设置 + + // 获取材质并设置参数 + const material = softBody.get_m_materials().at(0); + material.set_m_kLST(0.4); // 设置线性弹性系数 + material.set_m_kAST(0.4); // 设置角度弹性系数 + // material.set_m_kVST(0.2); // 设置体积弹性系数 + // material.set_m_flags(0); // 设置材质标志 + } + + onUpdate(): void { + if (!this._btBodyInited) return; + + // 根据锚点刚体的插值坐标平滑软体运动 + if (this._btRigidbody) { + this._btRigidbody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + const nowPos = this._btRigidbody.getWorldTransform().getOrigin(); + + TempPhyMath.fromBtVec(Physics.TEMP_TRANSFORM.getOrigin(), Vector3.HELP_0); + TempPhyMath.fromBtVec(nowPos, Vector3.HELP_1); + Vector3.sub(Vector3.HELP_0, Vector3.HELP_1, this._diff); + } + + const vertices = this._geometry.getAttribute(VertexAttributeName.position); + const normals = this._geometry.getAttribute(VertexAttributeName.normal); + + const nodes = this._btSoftbody.get_m_nodes(); + for (let i = 0; i < nodes.size(); i++) { + const node = nodes.at(i); + const pos = node.get_m_x(); + vertices.data[3 * i] = pos.x() + this._diff.x; + vertices.data[3 * i + 1] = pos.y() + this._diff.y; + vertices.data[3 * i + 2] = pos.z() + this._diff.z; + + const normal = node.get_m_n(); + normals.data[3 * i] = normal.x(); + normals.data[3 * i + 1] = normal.y(); + normals.data[3 * i + 2] = normal.z(); + } + + this._geometry.vertexBuffer.upload(VertexAttributeName.position, vertices); + this._geometry.vertexBuffer.upload(VertexAttributeName.normal, normals); + } + + private setAnchor() { + const anchorIndices = typeof this.anchorIndices[0] === 'number' + ? this.anchorIndices as number[] + : this.getCornerIndices(this.anchorIndices as CornerType[]); + + const nodesSize = this._btSoftbody.get_m_nodes().size(); + anchorIndices.forEach(nodeIndex => { + if (nodeIndex < 0 || nodeIndex >= nodesSize) { + console.error(`Invalid node index ${nodeIndex} for soft body`); + return; + } + }); + + let tm = this._btRigidbody.getWorldTransform(); + TempPhyMath.fromBtVec(tm.getOrigin(), Vector3.HELP_0); + Vector3.HELP_0.add(this.applyPosition, Vector3.HELP_1); + + TempPhyMath.fromBtQua(tm.getRotation(), Quaternion.HELP_0) + Quaternion.HELP_1.fromEulerAngles(this.applyRotation.x, this.applyRotation.y, this.applyRotation.z); + Quaternion.HELP_1.multiply(Quaternion.HELP_0, Quaternion.HELP_1); + + this._btSoftbody.rotate(TempPhyMath.toBtQua(Quaternion.HELP_1)); + this._btSoftbody.translate(TempPhyMath.toBtVec(Vector3.HELP_1)); + + anchorIndices.forEach((nodeIndex, idx) => { + const influence = Array.isArray(this.influence) ? (this.influence[idx] ?? 0.5) : this.influence; + const disableCollision = Array.isArray(this.disableCollision) ? (this.disableCollision[idx] ?? false) : this.disableCollision; + this._btSoftbody.appendAnchor(nodeIndex, this._btRigidbody, disableCollision, influence); + }); + } + + private getVertexIndex(x: number, y: number): number { + return y * (this._segmentW + 1) + x; + } + + /** + * 将 CornerType 数组转换成节点索引数组。 + * @param cornerList 需要转换的 CornerType 数组。 + * @returns 节点索引数组 + */ + private getCornerIndices(cornerList: CornerType[]): number[] { + const W = this._segmentW; + const H = this._segmentH; + return cornerList.map(corner => { + switch (corner) { + case 'left': + return this.getVertexIndex(0, Math.floor(H / 2)); + case 'right': + return this.getVertexIndex(W, Math.floor(H / 2)); + case 'top': + return this.getVertexIndex(Math.floor(W / 2), 0); + case 'bottom': + return this.getVertexIndex(Math.floor(W / 2), H); + case 'center': + return this.getVertexIndex(Math.floor(W / 2), Math.floor(H / 2)); + case 'leftTop': + return 0; + case 'rightTop': + return W; + case 'leftBottom': + return this.getVertexIndex(0, H); + case 'rightBottom': + return this.getVertexIndex(W, H); + default: + throw new Error('Invalid corner'); + } + }); + } + + /** + * 固定软体节点。 + * @param fixedNodeIndices 表示需要固定的节点索引或 CornerType 数组。 + */ + public applyFixedNodes(fixedNodeIndices: CornerType[] | number[]) { + // 确定索引数组 + const indexArray: number[] = typeof fixedNodeIndices[0] === 'number' + ? fixedNodeIndices as number[] + : this.getCornerIndices(fixedNodeIndices as CornerType[]); + + const nodes = this._btSoftbody.get_m_nodes(); + indexArray.forEach(i => { + if (i >= 0 && i < nodes.size()) { + nodes.at(i).get_m_v().setValue(0, 0, 0); + nodes.at(i).get_m_f().setValue(0, 0, 0); + nodes.at(i).set_m_im(0); + } else { + console.warn(`Index ${i} is out of bounds for nodes array.`); + } + }); + } + + /** + * 清除所有锚点,软体将会从附加的刚体上脱落 + */ + public clearAnchors() { + this._btSoftbody.get_m_anchors().clear(); + this._btRigidbody = null; + } + + public destroy(force?: boolean): void { + if (this._btBodyInited) { + (Physics.world as Ammo.btSoftRigidDynamicsWorld).removeSoftBody(this._btSoftbody); + Ammo.destroy(this._btSoftbody); + this._btSoftbody = null; + } + this._btBodyInited = false; + this._btRigidbody = null; + this._anchorRigidbody = null; + super.destroy(force); + } +} diff --git a/packages/physics/utils/CollisionShapeUtil.ts b/packages/physics/utils/CollisionShapeUtil.ts new file mode 100644 index 00000000..b1faf766 --- /dev/null +++ b/packages/physics/utils/CollisionShapeUtil.ts @@ -0,0 +1,411 @@ +import { Object3D, BoundUtil, Vector3, MeshRenderer, VertexAttributeName, PlaneGeometry, Quaternion, Matrix4, BoundingBox } from '@orillusion/core'; +import { Physics, Ammo } from '../Physics'; +import { TempPhyMath } from './TempPhyMath'; + +export interface ChildShape { + shape: Ammo.btCollisionShape; + position: Vector3; + rotation: Quaternion; +} + +/** + * CollisionShapeUtil + * 提供多种碰撞体构建功能 + */ +export class CollisionShapeUtil { + /** + * 创建静态平面碰撞形状,适用于静态无限平面的碰撞,如地面或墙壁。 + * @param planeNormal - 平面法向量,默认值为 Vector3.UP。 + * @param planeConstant - 平面常数,表示平面距离原点的距离,默认值为 0。 + * @returns Ammo.btStaticPlaneShape - 静态平面碰撞形状实例。 + */ + public static createStaticPlaneShape(planeNormal: Vector3 = Vector3.UP, planeConstant: number = 0) { + const normal = TempPhyMath.toBtVec(planeNormal); + const shape = new Ammo.btStaticPlaneShape(normal, planeConstant); + + return shape; + } + + /** + * 创建盒型碰撞形状,适用于具有明确尺寸的盒形物体。 + * 如果未指定尺寸,则使用三维对象的包围盒大小。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param size - 可选参数,盒型碰撞体的尺寸。 + * @returns Ammo.btBoxShape - 盒型碰撞形状实例。 + */ + public static createBoxShape(object3D: Object3D, size?: Vector3) { + size ||= this.calculateLocalBoundingBox(object3D).size; + const halfExtents = TempPhyMath.setBtVec(size.x / 2, size.y / 2, size.z / 2); + const shape = new Ammo.btBoxShape(halfExtents); + + return shape; + } + + /** + * 创建球型碰撞形状,适用于球形物体。 + * 如果未指定半径,则使用三维对象的包围盒半径 `X`。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param radius - 可选参数,球型碰撞体的半径。 + * @returns Ammo.btSphereShape - 球型碰撞形状实例。 + */ + public static createSphereShape(object3D: Object3D, radius?: number) { + radius ||= this.calculateLocalBoundingBox(object3D).extents.x; + const shape = new Ammo.btSphereShape(radius); + + return shape; + } + + /** + * 创建胶囊型碰撞形状,适用于胶囊形物体。 + * 如果未指定尺寸,则使用三维对象的包围盒半径 `X` 和高度 `Y`。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param radius - 可选参数,胶囊的半径。 + * @param height - 可选参数,胶囊中间的圆柱部分的高度。 + * @returns Ammo.btCapsuleShape - 胶囊型碰撞形状实例。 + */ + public static createCapsuleShape(object3D: Object3D, radius?: number, height?: number) { + let boundSize: Vector3 + if (!radius || !height) boundSize = this.calculateLocalBoundingBox(object3D).size; + + radius ||= boundSize.x / 2; + height ||= boundSize.y - radius * 2; + const shape = new Ammo.btCapsuleShape(radius, height); + + return shape; + } + + /** + * 创建圆柱型碰撞形状,适用于圆柱形物体。 + * 如果未指定尺寸,则使用三维对象的包围盒半径 `X` 和高度 `Y`。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param radius - 可选参数,圆柱的半径。 + * @param height - 可选参数,圆柱的完整高度。 + * @returns Ammo.btCylinderShape - 圆柱型碰撞形状实例。 + */ + public static createCylinderShape(object3D: Object3D, radius?: number, height?: number) { + let boundSize: Vector3 + if (!radius || !height) boundSize = this.calculateLocalBoundingBox(object3D).size; + + radius ||= boundSize.x / 2; + height ||= boundSize.y; + const halfExtents = TempPhyMath.setBtVec(radius, height / 2, radius); + const shape = new Ammo.btCylinderShape(halfExtents); + + return shape; + } + + /** + * 创建圆锥形碰撞形状,适用于圆锥形物体。 + * 如果未指定尺寸,则使用三维对象的包围盒半径 `X` 和高度 `Y`。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param radius - 可选参数,圆锥的半径。 + * @param height - 可选参数,圆锥的高度。 + * @returns Ammo.btConeShape - 圆锥形碰撞形状实例。 + */ + public static createConeShape(object3D: Object3D, radius?: number, height?: number) { + let boundSize: Vector3 + if (!radius || !height) boundSize = this.calculateLocalBoundingBox(object3D).size; + + radius ||= boundSize.x / 2; + height ||= boundSize.y; + const shape = new Ammo.btConeShape(radius, height); + + return shape; + } + + // 通过测试发现当前版本Ammo.btMultiSphereShape参数出现问题, + // 当前仅支持传入单个坐标,且球体们的位置会出现混乱, + // 传入坐标数组则完全无效。 + // 如果需要类似功能,可以使用复合或是网格形状。 + /** + * 创建多球体碰撞形状,适用于通过多个球体组合来近似复杂形状的情况。 + * 可以通过球体组合来创建近似椭球形状。 + * @param positions - 球体的位置数组。 + * @param radii - 球体的半径数组。 + * @returns Ammo.btMultiSphereShape - 多球体碰撞形状实例。 + */ + // public static createMultiSphereShape(positions: Vector3[], radii: number[]): Ammo.btMultiSphereShape { + // if (positions.length !== radii.length) { + // throw new Error("Positions and radii arrays must have the same length."); + // } + + // const btPositions = positions.map(pos => new Ammo.btVector3(pos.x, pos.y, pos.z)); + + // const shape = new Ammo.btMultiSphereShape((btPositions as any), radii, btPositions.length); + + // btPositions.forEach(pos => Ammo.destroy(pos)); + + // return shape; + // } + + /** + * 创建复合形状,将多个子形状组合成一个形状。 + * @param childShapes - 包含子形状实例与位置、旋转属性的数组。 + * @returns Ammo.btCompoundShape - 复合形状实例。 + */ + public static createCompoundShape(childShapes: ChildShape[]) { + const compoundShape = new Ammo.btCompoundShape(); + const transform = Physics.TEMP_TRANSFORM; + + childShapes.forEach(desc => { + transform.setIdentity(); + transform.setOrigin(TempPhyMath.toBtVec(desc.position)); + transform.setRotation(TempPhyMath.toBtQua(desc.rotation)); + + compoundShape.addChildShape(transform, desc.shape); + }); + + return compoundShape; + } + + /** + * 创建高度场形状,基于平面顶点数据模拟地形。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param heightScale - 高度缩放比例,默认值为 `1`。 + * @param upAxis - 高度场的上轴,默认值为 `1`。 + * @param hdt - 高度场的数据类型,默认值为 `Ammo.PHY_FLOAT`。 + * @param flipQuadEdges - 是否翻转四边形的边,默认值为 `false`。 + * @returns Ammo.btHeightfieldTerrainShape - 高度场形状实例。 + */ + public static createHeightfieldTerrainShape( + object3D: Object3D, + heightScale: number = 1, + upAxis: number = 1, + hdt: Ammo.PHY_ScalarType = 'PHY_FLOAT', + flipQuadEdges: boolean = false, + ) { + let geometry = object3D.getComponent(MeshRenderer)?.geometry; + + if (!(geometry instanceof PlaneGeometry)) throw new Error("Wrong geometry type"); + + const { width, height, segmentW, segmentH } = geometry; + let posAttrData = geometry.getAttribute(VertexAttributeName.position); + const heightData = new Float32Array(posAttrData.data.length / 3); + let minHeight = Infinity, maxHeight = -Infinity; + + for (let i = 0, count = posAttrData.data.length / 3; i < count; i++) { + let y = posAttrData.data[i * 3 + 1]; + heightData[i] = y; + if (y < minHeight) minHeight = y; + if (y > maxHeight) maxHeight = y; + } + + let ammoHeightData = Ammo._malloc(heightData.length * 4); + let ammoHeightDataF32 = new Float32Array(Ammo.HEAPF32.buffer, ammoHeightData, heightData.length); + ammoHeightDataF32.set(heightData); + + let shape = new Ammo.btHeightfieldTerrainShape( + segmentW + 1, + segmentH + 1, + ammoHeightData, + heightScale, + minHeight, + maxHeight, + upAxis, + hdt, + flipQuadEdges + ); + + let localScaling = TempPhyMath.setBtVec(width / segmentW, 1, height / segmentH); + shape.setLocalScaling(localScaling); + (shape as any).averageHeight = (minHeight + maxHeight) / 2; + + return shape; + } + + /** + * 创建凸包形状,适用于具有凹陷填充的模型。 + * 此形状适用于动态物体并提供快速的碰撞检测。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param modelVertices - 可选参数,提供碰撞体所需的顶点数据,默认为三维对象的顶点数据。 + * @returns Ammo.btConvexHullShape - 凸包形状实例。 + */ + public static createConvexHullShape(object3D: Object3D, modelVertices?: Float32Array) { + let vertices = modelVertices || this.getAllMeshVerticesAndIndices(object3D).vertices; + + let shape = new Ammo.btConvexHullShape(); + for (let i = 0, count = vertices.length / 3; i < count; i++) { + let point = TempPhyMath.setBtVec(vertices[3 * i], vertices[3 * i + 1], vertices[3 * i + 2]); + shape.addPoint(point, true); + } + + let scaling = TempPhyMath.toBtVec(object3D.localScale); + shape.setLocalScaling(scaling); + + return shape; + } + + /** + * 创建凸包网格形状,适用于需要复杂几何表示的动态物体。 + * 此形状不要求额外的凸包生成步骤,适用于凸的三角形网格。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param modelVertices - 可选参数,提供碰撞体所需的顶点数据。 + * @param modelIndices - 可选参数,提供碰撞体所需的索引数据。 + * @returns Ammo.btConvexTriangleMeshShape - 凸包网格形状实例。 + */ + public static createConvexTriangleMeshShape(object3D: Object3D, modelVertices?: Float32Array, modelIndices?: Uint16Array): Ammo.btBvhTriangleMeshShape { + // 检查 modelVertices 和 modelIndices 是否同时被提供或同时未提供 + if ((modelVertices && !modelIndices) || (!modelVertices && modelIndices)) { + console.warn('createConvexTriangleMeshShape: Both modelVertices and modelIndices must be provided or neither should be provided.'); + } + + const { vertices, indices } = (modelVertices && modelIndices) + ? { vertices: modelVertices, indices: modelIndices } + : this.getAllMeshVerticesAndIndices(object3D); + + const triangleMesh = this.buildTriangleMesh(vertices, indices); + const shape = new Ammo.btConvexTriangleMeshShape(triangleMesh, true); + + const scaling = TempPhyMath.toBtVec(object3D.localScale); + shape.setLocalScaling(scaling); + + return shape; + } + + /** + * 创建边界体积层次(BVH)网格形状,适用于需要复杂几何表示的静态物体。 + * 此形状适合大规模静态网格,但对动态对象不适用。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param modelVertices - 可选参数,提供碰撞体所需的顶点数据。 + * @param modelIndices - 可选参数,提供碰撞体所需的索引数据。 + * @returns Ammo.btBvhTriangleMeshShape - BVH 网格形状实例。 + */ + public static createBvhTriangleMeshShape(object3D: Object3D, modelVertices?: Float32Array, modelIndices?: Uint16Array): Ammo.btBvhTriangleMeshShape { + // 检查 modelVertices 和 modelIndices 是否同时被提供或同时未提供 + if ((modelVertices && !modelIndices) || (!modelVertices && modelIndices)) { + console.warn('createBvhTriangleMeshShape: Both modelVertices and modelIndices must be provided or neither should be provided.'); + } + + const { vertices, indices } = (modelVertices && modelIndices) + ? { vertices: modelVertices, indices: modelIndices } + : this.getAllMeshVerticesAndIndices(object3D); + + const triangleMesh = this.buildTriangleMesh(vertices, indices); + const shape = new Ammo.btBvhTriangleMeshShape(triangleMesh, true, true); + + const scaling = TempPhyMath.toBtVec(object3D.localScale); + shape.setLocalScaling(scaling); + + return shape; + } + + /** + * 创建 GImpact 网格形状,适用于需要复杂几何表示的动态物体。 + * 基于 GIMPACT 算法,可以用于复杂的三角网格碰撞检测,包括动态物体的交互,此形状性能消耗较高,但提供更精确的碰撞检测。 + * @param object3D - 用于创建碰撞体的三维对象。 + * @param modelVertices - 可选参数,提供碰撞体所需的顶点数据。 + * @param modelIndices - 可选参数,提供碰撞体所需的索引数据。 + * @returns Ammo.btGImpactMeshShape - GImpact 网格形状实例。 + */ + public static createGImpactMeshShape(object3D: Object3D, modelVertices?: Float32Array, modelIndices?: Uint16Array): Ammo.btGImpactMeshShape { + // 检查 modelVertices 和 modelIndices 是否同时被提供或同时未提供 + if ((modelVertices && !modelIndices) || (!modelVertices && modelIndices)) { + console.warn('createGImpactMeshShape: Both modelVertices and modelIndices must be provided or neither should be provided.'); + } + + const { vertices, indices } = (modelVertices && modelIndices) + ? { vertices: modelVertices, indices: modelIndices } + : this.getAllMeshVerticesAndIndices(object3D); + + const triangleMesh = this.buildTriangleMesh(vertices, indices); + const shape = new Ammo.btGImpactMeshShape(triangleMesh); + shape.updateBound(); + + const scaling = TempPhyMath.toBtVec(object3D.localScale); + shape.setLocalScaling(scaling); + + return shape; + } + + /** + * 构建 btTriangleMesh 对象,用于创建网格形状。 + * @param vertices - 顶点数据,按 xyz 顺序排列。 + * @param indices - 索引数据,定义三角形的顶点索引。 + * @returns Ammo.btTriangleMesh - 构建的三角形网格。 + */ + public static buildTriangleMesh(vertices: Float32Array, indices: Uint16Array): Ammo.btTriangleMesh { + let triangleMesh = new Ammo.btTriangleMesh(); + + for (let i = 0; i < indices.length; i += 3) { + const index0 = indices[i] * 3; + const index1 = indices[i + 1] * 3; + const index2 = indices[i + 2] * 3; + + const v0 = TempPhyMath.setBtVec(vertices[index0], vertices[index0 + 1], vertices[index0 + 2], TempPhyMath.tmpVecA); + const v1 = TempPhyMath.setBtVec(vertices[index1], vertices[index1 + 1], vertices[index1 + 2], TempPhyMath.tmpVecB); + const v2 = TempPhyMath.setBtVec(vertices[index2], vertices[index2 + 1], vertices[index2 + 2], TempPhyMath.tmpVecC); + + triangleMesh.addTriangle(v0, v1, v2, true); + } + + return triangleMesh; + } + + /** + * 获取3D对象所有网格的顶点与索引。 + * @param object3D - 三维对象,通常是模型对象。 + * @returns 顶点数据和索引数据。 + */ + public static getAllMeshVerticesAndIndices(object3D: Object3D) { + let mr = object3D.getComponents(MeshRenderer); + + if (mr.length === 1) { + return { + vertices: mr[0].geometry.getAttribute(VertexAttributeName.position).data as Float32Array, + indices: mr[0].geometry.getAttribute(VertexAttributeName.indices).data as Uint16Array + }; + } + + let totalVertexLength = 0; + let totalIndexLength = 0; + + // 计算总顶点数和总索引数 + mr.forEach(e => { + totalVertexLength += e.geometry.getAttribute(VertexAttributeName.position).data.length; + totalIndexLength += e.geometry.getAttribute(VertexAttributeName.indices).data.length; + }); + + let vertices = new Float32Array(totalVertexLength); + let indices = new Uint16Array(totalIndexLength); + + let vertexOffset = 0; + let indexOffset = 0; + let currentIndexOffset = 0; + + // 合并顶点和索引数据 + mr.forEach(e => { + let vertexArray = e.geometry.getAttribute(VertexAttributeName.position).data; + vertices.set(vertexArray, vertexOffset); + vertexOffset += vertexArray.length; + + let indexArray = e.geometry.getAttribute(VertexAttributeName.indices).data; + for (let i = 0; i < indexArray.length; i++) { + indices[indexOffset + i] = indexArray[i] + currentIndexOffset; + } + indexOffset += indexArray.length; + currentIndexOffset += vertexArray.length / 3; + }); + + return { vertices, indices }; + } + + /** + * 计算三维对象的局部包围盒 + * @param object3D - 三维对象 + * @returns 局部包围盒 + */ + private static calculateLocalBoundingBox(object3D: Object3D): BoundingBox { + // 如果对象存在渲染节点(已添加至场景)并且没有子对象,直接返回其几何包围盒 + if (object3D.renderNode && !object3D.numChildren) { + return object3D.renderNode.geometry.bounds; + } + + // 通过旋转重置,计算对象及其子对象的包围盒,此时包围盒结果与局部包围盒相同 + let originalRotation = object3D.localRotation.clone(); + object3D.localRotation = Vector3.ZERO; + let bounds = BoundUtil.genMeshBounds(object3D); + object3D.localRotation = originalRotation; + return bounds; + } + +} diff --git a/packages/physics/utils/ContactProcessedUtil.ts b/packages/physics/utils/ContactProcessedUtil.ts new file mode 100644 index 00000000..8acce64a --- /dev/null +++ b/packages/physics/utils/ContactProcessedUtil.ts @@ -0,0 +1,188 @@ +import { Physics, Ammo } from '../Physics'; + +type Callback = (contactPoint: Ammo.btManifoldPoint, bodyA: Ammo.btRigidBody, bodyB: Ammo.btRigidBody) => void; + +/** + * 碰撞处理工具 + */ +export class ContactProcessedUtil { + private static callbacks: Map = new Map(); + private static ignoredPointers: Set = new Set(); + private static contactProcessedCallbackPointer: number | null = null; + + /** + * 注册碰撞事件 + * @param pointer 物理对象指针 + * @param callback 事件回调 + */ + public static registerCollisionCallback(pointer: number, callback: Callback): void { + if (pointer == null) return; + + ContactProcessedUtil.callbacks.set(pointer, callback); + if (ContactProcessedUtil.callbacks.size === 1) { + // 第一个注册的回调,注册碰撞处理回调 + ContactProcessedUtil.registerContactProcessedCallback(); + } + } + + /** + * 注销碰撞事件 + * @param pointer 物理对象指针 + */ + public static unregisterCollisionCallback(pointer: number): void { + ContactProcessedUtil.callbacks.delete(pointer); + if (ContactProcessedUtil.callbacks.size === 0) { + // 最后一个注销的回调,禁用碰撞处理回调 + ContactProcessedUtil.unregisterContactProcessedCallback(); + } + } + + /** + * 注册全局碰撞处理回调 + */ + private static registerContactProcessedCallback(): void { + if (ContactProcessedUtil.contactProcessedCallbackPointer === null) { + ContactProcessedUtil.contactProcessedCallbackPointer = Ammo.addFunction(ContactProcessedUtil.contactProcessedCallback); + Physics.world.setContactProcessedCallback(ContactProcessedUtil.contactProcessedCallbackPointer); + } + } + + /** + * 注销全局碰撞处理回调 + */ + private static unregisterContactProcessedCallback(): void { + if (ContactProcessedUtil.contactProcessedCallbackPointer !== null) { + Physics.world.setContactProcessedCallback(null); // 禁用回调 + ContactProcessedUtil.contactProcessedCallbackPointer = null; + } + } + + /** + * 将指针添加到忽略集合中,添加后,任何物体与该指针对象碰撞时都无法触发碰撞事件 + * @param pointer 物理对象指针 + */ + public static addIgnoredPointer(pointer: number): void { + if (pointer != null) { + ContactProcessedUtil.ignoredPointers.add(pointer); + } + } + + /** + * 从忽略集合中移除指针 + * @param pointer 物理对象指针 + */ + public static removeIgnoredPointer(pointer: number): void { + ContactProcessedUtil.ignoredPointers.delete(pointer); + } + + /** + * 检查指针是否在忽略集合中 + * @param pointer 物理对象指针 + */ + public static isIgnored(pointer: number): boolean { + return ContactProcessedUtil.ignoredPointers.has(pointer); + } + + /** + * 检查指针是否注册了碰撞事件 + * @param pointer 物理对象指针 + */ + public static isCollision(pointer: number): boolean { + return ContactProcessedUtil.callbacks.has(pointer); + } + + /** + * 全局接触(碰撞)事件回调函数 + */ + private static contactProcessedCallback(cpPtr: number, colObj0WrapPtr: number, colObj1WrapPtr: number): number { + // 检查是否需要忽略 + if (ContactProcessedUtil.ignoredPointers.has(colObj0WrapPtr) || ContactProcessedUtil.ignoredPointers.has(colObj1WrapPtr)) { + return 0; + } + + // 通过碰撞对象包装器指针获取其注册的事件 + const callbackA = ContactProcessedUtil.callbacks.get(colObj0WrapPtr); + const callbackB = ContactProcessedUtil.callbacks.get(colObj1WrapPtr); + + // 排除均未注册碰撞事件的碰撞对 + if (callbackA || callbackB) { + // 指针转换 + const cp = Ammo.wrapPointer(cpPtr, Ammo.btManifoldPoint); + const bodyA = Ammo.wrapPointer(colObj0WrapPtr, Ammo.btRigidBody); + const bodyB = Ammo.wrapPointer(colObj1WrapPtr, Ammo.btRigidBody); + + callbackA?.(cp, bodyA, bodyB); + callbackB?.(cp, bodyB, bodyA); + } + + return 0; // 返回0表示已处理本次碰撞 + } + + /** + * 执行一次性的碰撞测试。 + * 如果提供了 bodyB,则检测 bodyA 与 bodyB 是否碰撞。 + * 否则,检测 bodyA 是否与其他所有刚体碰撞。 + * @param bodyA - 第一个刚体。 + * @param bodyB - (可选)第二个刚体。 + * @returns 如果发生碰撞,返回包含碰撞信息的对象;否则返回 null。 + */ + public static performCollisionTest(bodyA: Ammo.btRigidBody, bodyB?: Ammo.btRigidBody) { + const callback = new Ammo.ConcreteContactResultCallback(); + let collisionDetected: { + cpPtr: number, + colObj0Wrap: Ammo.btCollisionObjectWrapper, + colObj1Wrap: Ammo.btCollisionObjectWrapper, + partId0: number, + index0: number, + partId1: number, + index1: number + } | null = null; + + callback.addSingleResult = (cp, colObj0Wrap, partId0, index0, colObj1Wrap, partId1, index1) => { + let collisionObjectWrapperA = Ammo.wrapPointer(colObj0Wrap as unknown as number, Ammo.btCollisionObjectWrapper) + let collisionObjectWrapperB = Ammo.wrapPointer(colObj1Wrap as unknown as number, Ammo.btCollisionObjectWrapper) + collisionDetected = { + cpPtr: cp as unknown as number, + colObj0Wrap: collisionObjectWrapperA, + colObj1Wrap: collisionObjectWrapperB, + partId0, + index0, + partId1, + index1 + }; + + return 0; + }; + + if (bodyB) { + Physics.world.contactPairTest(bodyA, bodyB, callback); + } else { + Physics.world.contactTest(bodyA, callback); + } + + Ammo.destroy(callback); + + return collisionDetected; + } + + /** + * 碰撞检测,判断两个刚体是否正在发生碰撞 + * @param bodyA + * @param bodyB + * @returns boolean + */ + public static checkCollision(bodyA: Ammo.btRigidBody, bodyB: Ammo.btRigidBody): boolean { + const dispatcher = Physics.world.getDispatcher(); + const manifoldCount = dispatcher.getNumManifolds(); + for (let i = 0; i < manifoldCount; i++) { + const manifold = dispatcher.getManifoldByIndexInternal(i); + const rbA = Ammo.castObject(manifold.getBody0(), Ammo.btRigidBody); + const rbB = Ammo.castObject(manifold.getBody1(), Ammo.btRigidBody); + if ((rbA === bodyA && rbB === bodyB) || (rbA === bodyB && rbB === bodyA)) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/packages/physics/utils/RigidBodyMapping.ts b/packages/physics/utils/RigidBodyMapping.ts new file mode 100644 index 00000000..446dec80 --- /dev/null +++ b/packages/physics/utils/RigidBodyMapping.ts @@ -0,0 +1,68 @@ +import { Object3D, BiMap } from '@orillusion/core'; +import { Ammo } from "../Physics"; + +/** + * A bidirectional mapping between RigidBody objects and 3D objects. + */ +export class RigidBodyMapping { + private static mapping: BiMap = new BiMap(); + + /** + * Retrieves the entire mapping of all RigidBody objects. + * @returns A map of RigidBody objects to 3D objects. + */ + public static get getAllPhysicsObjectMap(): Map { + return this.mapping["negtive"]; + } + + /** + * Retrieves the entire mapping of all 3D objects. + * @returns A map of 3D objects to RigidBody objects. + */ + public static get getAllGraphicObjectMap(): Map { + return this.mapping; + } + + /** + * Adds a mapping between a 3D object and a RigidBody object. + * @param object3D The 3D object. + * @param physics The RigidBody object. + */ + public static addMapping(object3D: Object3D, physics: Ammo.btRigidBody) { + this.mapping.set(object3D, physics); + } + + /** + * Retrieves the RigidBody object associated with a given 3D object. + * @param object3D The 3D object. + * @returns The associated RigidBody object, or undefined if not found. + */ + public static getPhysicsObject(object3D: Object3D): Ammo.btRigidBody | undefined { + return this.mapping.get(object3D); + } + + /** + * Retrieves the 3D object associated with a given RigidBody object. + * @param physics The RigidBody object. + * @returns The associated 3D object, or undefined if not found. + */ + public static getGraphicObject(physics: Ammo.btRigidBody): Object3D | undefined { + return this.mapping.getKey(physics); + } + + /** + * Removes the mapping associated with a given 3D object. + * @param object3D The 3D object. + */ + public static removeMappingByGraphic(object3D: Object3D) { + this.mapping.delete(object3D); + } + + /** + * Removes the mapping associated with a given RigidBody object. + * @param physics The RigidBody object. + */ + public static removeMappingByPhysics(physics: Ammo.btRigidBody) { + this.mapping.deleteValue(physics); + } +} diff --git a/packages/physics/utils/RigidBodyUtil.ts b/packages/physics/utils/RigidBodyUtil.ts new file mode 100644 index 00000000..9971a5c7 --- /dev/null +++ b/packages/physics/utils/RigidBodyUtil.ts @@ -0,0 +1,169 @@ +import { Vector3, Quaternion, Object3D } from '@orillusion/core'; +import { Physics, Ammo } from '../Physics'; +import { TempPhyMath } from './TempPhyMath'; + +/** + * 提供一系列AMMO刚体相关的方法 + */ +export class RigidBodyUtil { + /** + * 创建 Ammo 刚体。 + * @param object3D - 三维对象。 + * @param shape - 碰撞形状。 + * @param mass - 碰撞体的质量。 + * @param position - 可选参数,刚体的位置,默认使用三维对象的 `localPosition` + * @param rotation - 可选参数,刚体的旋转,默认使用三维对象的 `localRotation` + * @returns 新创建的 Ammo.btRigidBody 对象。 + */ + public static createRigidBody(object3D: Object3D, shape: Ammo.btCollisionShape, mass: number, position?: Vector3, rotation?: Vector3 | Quaternion): Ammo.btRigidBody { + position ||= object3D.localPosition; + rotation ||= object3D.localRotation; + + const transform = Physics.TEMP_TRANSFORM; + transform.setIdentity(); + transform.setOrigin(TempPhyMath.toBtVec(position)); + let rotQuat = (rotation instanceof Vector3) ? TempPhyMath.eulerToBtQua(rotation) : TempPhyMath.toBtQua(rotation); + transform.setRotation(rotQuat); + + const motionState = new Ammo.btDefaultMotionState(transform); + const localInertia = TempPhyMath.zeroBtVec(); + shape.calculateLocalInertia(mass, localInertia); + + const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia); + const bodyRb = new Ammo.btRigidBody(rbInfo); + + return bodyRb; + } + + /** + * 更新刚体的位置和旋转。 + * 此函数将新的位置和旋转应用到刚体上。 + * @param bodyRb - 刚体对象。 + * @param position - 刚体的新位置,以 Vector3 形式表示。 + * @param rotation - 刚体的新旋转,可选,可以是 Vector3 形式表示的欧拉角(将自动转换为四元数),默认为四元数零值。 + * @param clearFV - 清除力和速度,可选,默认为 false 。 + */ + public static updateTransform(bodyRb: Ammo.btRigidBody, position: Vector3, rotation: Vector3 | Quaternion, clearFV?: boolean) { + rotation ||= Quaternion._zero; + + const transform = bodyRb.getWorldTransform(); + transform.setOrigin(TempPhyMath.toBtVec(position)); + let rotQuat = (rotation instanceof Vector3) ? TempPhyMath.eulerToBtQua(rotation) : TempPhyMath.toBtQua(rotation); + transform.setRotation(rotQuat); + + bodyRb.setWorldTransform(transform); + bodyRb.getMotionState().setWorldTransform(transform); + bodyRb.activate(); + + if (clearFV) { + this.clearForcesAndVelocities(bodyRb); + } + } + + /** + * 更新刚体位置 + * @param bodyRb + * @param value + */ + public static updatePosition(bodyRb: Ammo.btRigidBody, value: Vector3) { + if (bodyRb.isKinematicObject()) { + bodyRb.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + Physics.TEMP_TRANSFORM.setOrigin(TempPhyMath.toBtVec(value)); + bodyRb.getMotionState().setWorldTransform(Physics.TEMP_TRANSFORM); + } else { + bodyRb.getWorldTransform().getOrigin().setValue(value.x, value.y, value.z); + bodyRb.activate(); + } + } + + /** + * 更新刚体旋转 + * @param bodyRb + * @param value + */ + public static updateRotation(bodyRb: Ammo.btRigidBody, value: Vector3) { + if (bodyRb.isKinematicObject()) { + bodyRb.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + Physics.TEMP_TRANSFORM.setRotation(TempPhyMath.eulerToBtQua(value)); + bodyRb.getMotionState().setWorldTransform(Physics.TEMP_TRANSFORM); + } else { + bodyRb.getWorldTransform().setRotation(TempPhyMath.eulerToBtQua(value)); + bodyRb.activate(); + } + } + + /** + * 更新刚体缩放 + * @param bodyRb + * @param value + * @param mass + */ + public static updateScale(bodyRb: Ammo.btRigidBody, value: Vector3, mass: number) { + const shape = bodyRb.getCollisionShape(); + shape.setLocalScaling(TempPhyMath.toBtVec(value)); + if (mass > 0) { + const localInertia = TempPhyMath.zeroBtVec(); + shape.calculateLocalInertia(mass, localInertia); + bodyRb.setMassProps(mass, localInertia); + bodyRb.activate(); + } + } + + /** + * 清除力和速度 + * @param bodyRb + */ + public static clearForcesAndVelocities(bodyRb: Ammo.btRigidBody) { + bodyRb.clearForces(); + bodyRb.setLinearVelocity(TempPhyMath.zeroBtVec()); + bodyRb.setAngularVelocity(TempPhyMath.zeroBtVec()); + } + + /** + * 激活物理世界中的全部碰撞对 + */ + public static activateCollisionBodies(): void { + const dispatcher = Physics.world.getDispatcher(); + const numManifolds = dispatcher.getNumManifolds(); + + for (let i = 0; i < numManifolds; i++) { + const manifold = dispatcher.getManifoldByIndexInternal(i); + + const body0 = Ammo.castObject(manifold.getBody0(), Ammo.btRigidBody); + const body1 = Ammo.castObject(manifold.getBody1(), Ammo.btRigidBody); + + if (body0 && body0.getMotionState()) { + body0.activate(); + } + + if (body1 && body1.getMotionState()) { + body1.activate(); + } + } + } + + /** + * 销毁刚体及其状态和形状 + * @param bodyRb + */ + public static destroyRigidBody(bodyRb: Ammo.btRigidBody): void { + if (!bodyRb) return console.warn('There is no rigid body'); + + Physics.world.removeRigidBody(bodyRb); + Ammo.destroy(bodyRb.getCollisionShape()); + Ammo.destroy(bodyRb.getMotionState()); + Ammo.destroy(bodyRb); + } + + /** + * 销毁约束 + * @param constraint + */ + public static destroyConstraint(constraint: Ammo.btTypedConstraint) { + if (constraint) { + Physics.world.removeConstraint(constraint); + Ammo.destroy(constraint); + constraint = null; + } + } +} diff --git a/packages/physics/utils/TempPhyMath.ts b/packages/physics/utils/TempPhyMath.ts new file mode 100644 index 00000000..dae04212 --- /dev/null +++ b/packages/physics/utils/TempPhyMath.ts @@ -0,0 +1,105 @@ +import { Vector3, Quaternion, DEGREES_TO_RADIANS } from '@orillusion/core'; +import { Ammo } from '../Physics'; + +/** + * Temporary Physics Math Utility + * + * 提供临时的 Ammo btVector3 和 btQuaternion 实例,并支持与引擎数据相互转换 + */ +export class TempPhyMath { + public static readonly tmpVecA: Ammo.btVector3; + public static readonly tmpVecB: Ammo.btVector3; + public static readonly tmpVecC: Ammo.btVector3; + public static readonly tmpVecD: Ammo.btVector3; + public static readonly tmpQuaA: Ammo.btQuaternion; + public static readonly tmpQuaB: Ammo.btQuaternion; + + /** + * 初始化 Ammo 后创建预定义的 btVector3 和 btQuaternion 实例,以便复用 + */ + public static init() { + (this as any).tmpVecA = new Ammo.btVector3(0, 0, 0); + (this as any).tmpVecB = new Ammo.btVector3(0, 0, 0); + (this as any).tmpVecC = new Ammo.btVector3(0, 0, 0); + (this as any).tmpVecD = new Ammo.btVector3(0, 0, 0); + (this as any).tmpQuaA = new Ammo.btQuaternion(0, 0, 0, 1); + (this as any).tmpQuaB = new Ammo.btQuaternion(0, 0, 0, 1); + } + + /** + * Quaternion to Ammo.btQuaternion + */ + public static toBtQua(qua: Quaternion, btQua?: Ammo.btQuaternion): Ammo.btQuaternion { + btQua ||= this.tmpQuaA; + btQua.setValue(qua.x, qua.y, qua.z, qua.w); + return btQua; + } + + /** + * Vector3 to Ammo.btVector3 + */ + public static toBtVec(vec: Vector3, btVec?: Ammo.btVector3): Ammo.btVector3 { + btVec ||= this.tmpVecA; + btVec.setValue(vec.x, vec.y, vec.z); + return btVec; + } + + /** + * Set Ammo.btVector3 using x, y, z + */ + public static setBtVec(x: number, y: number, z: number, btVec?: Ammo.btVector3): Ammo.btVector3 { + btVec ||= this.tmpVecA; + btVec.setValue(x, y, z); + return btVec; + } + + /** + * Set Ammo.btQuaternion using x, y, z, w + */ + public static setBtQua(x: number, y: number, z: number, w: number, btQua?: Ammo.btQuaternion): Ammo.btQuaternion { + btQua ||= this.tmpQuaA; + btQua.setValue(x, y, z, w); + return btQua; + } + + /** + * Ammo.btVector3 to Vector3 + */ + public static fromBtVec(btVec: Ammo.btVector3, vec?: Vector3): Vector3 { + vec ||= new Vector3(); + vec.set(btVec.x(), btVec.y(), btVec.z()); + return vec; + } + + /** + * Ammo.btQuaternion to Quaternion + */ + public static fromBtQua(btQua: Ammo.btQuaternion, qua?: Quaternion): Quaternion { + qua ||= new Quaternion(); + qua.set(btQua.x(), btQua.y(), btQua.z(), btQua.w()); + return qua; + } + + /** + * Euler Vector3 to Ammo.Quaternion + */ + public static eulerToBtQua(vec: Vector3, qua?: Ammo.btQuaternion): Ammo.btQuaternion { + qua ||= this.tmpQuaA; + qua.setEulerZYX(vec.z * DEGREES_TO_RADIANS, vec.y * DEGREES_TO_RADIANS, vec.x * DEGREES_TO_RADIANS); + return qua; + } + + /** + * Sets the given Ammo.btVector3 to (0, 0, 0) + */ + public static zeroBtVec(btVec?: Ammo.btVector3): Ammo.btVector3 { + return this.setBtVec(0, 0, 0, btVec); + } + + /** + * Sets the given Ammo.btQuaternion to (0, 0, 0, 1) + */ + public static resetBtQua(btQua?: Ammo.btQuaternion): Ammo.btQuaternion { + return this.setBtQua(0, 0, 0, 1, btQua); + } +} diff --git a/packages/physics/visualDebug/DebugDrawModeEnum.ts b/packages/physics/visualDebug/DebugDrawModeEnum.ts new file mode 100644 index 00000000..91287730 --- /dev/null +++ b/packages/physics/visualDebug/DebugDrawModeEnum.ts @@ -0,0 +1,90 @@ +export type DebugDrawerOptions = Partial<{ + /** + * 启用状态,默认 false + */ + enable: boolean; + /** + * 设置 debug 模式,默认值为 1 (DrawWireframe: 绘制物理对象的线框) + */ + debugDrawMode: DebugDrawMode; + /** + * 更新频率,默认每 1 帧更新一次 + */ + updateFreq: number; + /** + * 最多渲染的线条,默认 25,000 (超过 32,000 可能会导致错误 V0.8.2) + */ + maxLineCount: number; +}>; + + +export enum DebugDrawMode { + /** + * 不显示调试信息 + */ + NoDebug = 0, + /** + * 绘制物理对象的线框 + */ + DrawWireframe = 1, + /** + * 绘制物理对象的包围盒(AABB) + */ + DrawAabb = 2, + /** + * 绘制特征点文本 + */ + DrawFeaturesText = 4, + /** + * 绘制接触点 + */ + DrawContactPoints = 8, + /** + * 禁用去激活 + */ + NoDeactivation = 16, + /** + * 不显示帮助文本 + */ + NoHelpText = 32, + /** + * 绘制文本信息 + */ + DrawText = 64, + /** + * 显示性能计时信息 + */ + ProfileTimings = 128, + /** + * 启用 SAT 比较 + */ + EnableSatComparison = 256, + /** + * 禁用 Bullet 的 LCP 算法 + */ + DisableBulletLCP = 512, + /** + * 启用连续碰撞检测 + */ + EnableCCD = 1024, + /** + * 绘制约束 + */ + DrawConstraints = 2048, + /** + * 绘制约束限制 + */ + DrawConstraintLimits = 4096, + /** + * 绘制快速剔除代理的 AABB + */ + FastWireframe = 8192, + /** + * 绘制动态 AABB 树 + */ + DrawAabbDynamic = 16384, + /** + * 绘制软体物理 + */ + DrawSoftBodies = 32768, +} \ No newline at end of file diff --git a/packages/physics/visualDebug/PhysicsDebugDrawer.ts b/packages/physics/visualDebug/PhysicsDebugDrawer.ts new file mode 100644 index 00000000..ed5f42d1 --- /dev/null +++ b/packages/physics/visualDebug/PhysicsDebugDrawer.ts @@ -0,0 +1,172 @@ +import { Object3D, Vector3, Color, GetCountInstanceID } from "@orillusion/core"; +import { Ammo } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { DebugDrawMode, DebugDrawerOptions } from "./DebugDrawModeEnum"; + +export class PhysicsDebugDrawer { + private debugDrawer: Ammo.DebugDrawer; + private _enable: boolean = true; + private frameCount: number = 0; + /** + * A graphic object used to draw lines + * + * Type: `Graphic3D` + */ + private graphic3D; + + // Exceeding 32,000 lines may cause engine crash. + private lineCount: number = 0; + private lineNameList: string[] = []; + private readonly _tmpCor: Color = new Color(); + private readonly _tmpVecA: Vector3 = new Vector3(); + private readonly _tmpVecB: Vector3 = new Vector3(); + + public world: Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld; + public debugMode: number; + public updateFreq: number; + public maxLineCount: number; + + public readonly debugModeList = { + NoDebug: DebugDrawMode.NoDebug, + DrawWireframe: DebugDrawMode.DrawWireframe, + DrawAabb: DebugDrawMode.DrawAabb, + DrawFeaturesText: DebugDrawMode.DrawFeaturesText, + DrawContactPoints: DebugDrawMode.DrawContactPoints, + NoDeactivation: DebugDrawMode.NoDeactivation, + NoHelpText: DebugDrawMode.NoHelpText, + DrawText: DebugDrawMode.DrawText, + ProfileTimings: DebugDrawMode.ProfileTimings, + EnableSatComparison: DebugDrawMode.EnableSatComparison, + DisableBulletLCP: DebugDrawMode.DisableBulletLCP, + EnableCCD: DebugDrawMode.EnableCCD, + DrawConstraints: DebugDrawMode.DrawConstraints, + DrawConstraintLimits: DebugDrawMode.DrawConstraintLimits, + FastWireframe: DebugDrawMode.FastWireframe, + DrawAabbDynamic: DebugDrawMode.DrawAabbDynamic, + DrawSoftBodies: DebugDrawMode.DrawSoftBodies, + }; + + constructor(world: Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld, graphic3D: Object3D, options: DebugDrawerOptions = {}) { + if (!graphic3D) throw new Error("Physics Debug Drawer requires a Graphic3D object."); + + this.world = world; + this.graphic3D = graphic3D; + + this._enable = options.enable || false; + this.debugMode = options.debugDrawMode ?? DebugDrawMode.DrawWireframe; + this.updateFreq = options.updateFreq || 1; + this.maxLineCount = options.maxLineCount || 25000; + + this.debugDrawer = new Ammo.DebugDrawer(); + this.debugDrawer.drawLine = this.drawLine.bind(this); + this.debugDrawer.drawContactPoint = this.drawContactPoint.bind(this); + this.debugDrawer.reportErrorWarning = this.reportErrorWarning.bind(this); + this.debugDrawer.draw3dText = this.draw3dText.bind(this); + this.debugDrawer.setDebugMode = this.setDebugMode.bind(this); + this.debugDrawer.getDebugMode = this.getDebugMode.bind(this); + + this.world.setDebugDrawer(this.debugDrawer); + } + + /** + * 启用/禁用物理调试绘制 + */ + public set enable(value: boolean) { + this._enable = value; + if (this.lineNameList.length > 0) { + this.clearLines() + } + + // this.world.setDebugDrawer(value ? this.debugDrawer : null); + + } + + public get enable() { + return this._enable; + } + + public setDebugMode(debugMode: DebugDrawMode): void { + this.debugMode = debugMode; + } + + public getDebugMode(): DebugDrawMode { + return this.debugMode; + } + + public update(): void { + if (!this._enable) return; + + if (++this.frameCount % this.updateFreq !== 0) return; + + this.clearLines(); + + this.world.debugDrawWorld(); + + // console.log(this.lineCount); + this.lineCount = 0; + } + + private drawLine(from: Ammo.btVector3, to: Ammo.btVector3, color: Ammo.btVector3): void { + if (!this._enable) return; + + if (++this.lineCount > this.maxLineCount) return; // console.log(`超出限制,正在渲染第 ${this.lineCount} 条线`); + + const fromVector = Ammo.wrapPointer(from as unknown as number, Ammo.btVector3); + const toVector = Ammo.wrapPointer(to as unknown as number, Ammo.btVector3); + const colorVector = Ammo.wrapPointer(color as unknown as number, Ammo.btVector3); + + const lineColor = this._tmpCor.copyFromVector(TempPhyMath.fromBtVec(colorVector, this._tmpVecA)); + const p0 = TempPhyMath.fromBtVec(fromVector, this._tmpVecA); + const p1 = TempPhyMath.fromBtVec(toVector, this._tmpVecB); + + const name = `AmmoLine_${this.lineCount}`; + this.lineNameList.push(name); + // Engine3D.views[this.viewIndex].graphic3D.drawLines(name, [p0, p1], lineColor); + this.graphic3D.drawLines(name, [p0, p1], lineColor); + + } + + private drawContactPoint(pointOnB: Ammo.btVector3, normalOnB: Ammo.btVector3, distance: number, lifeTime: number, color: Ammo.btVector3): void { + if (!this._enable) return; + + if (++this.lineCount > this.maxLineCount) return; // console.log(`超出限制,正在渲染第 ${this.lineCount} 条线`); + + const colorVector = Ammo.wrapPointer(color as unknown as number, Ammo.btVector3); + const pointOnBVector = Ammo.wrapPointer(pointOnB as unknown as number, Ammo.btVector3); + const normalOnBVector = Ammo.wrapPointer(normalOnB as unknown as number, Ammo.btVector3); + + const lineColor = this._tmpCor.copyFromVector(TempPhyMath.fromBtVec(colorVector, this._tmpVecA)); + const p0 = TempPhyMath.fromBtVec(pointOnBVector, this._tmpVecA); + const normal = TempPhyMath.fromBtVec(normalOnBVector, this._tmpVecB); + const p1 = p0.add(normal.multiplyScalar(distance), this._tmpVecB); + + const name = `AmmoContactPoint_${GetCountInstanceID()}`; + this.lineNameList.push(name); + + // Engine3D.views[this.viewIndex].graphic3D.drawLines(name, [p0, p1], lineColor); + this.graphic3D.drawLines(name, [p0, p1], lineColor); + + // 在接触点生命周期结束后进行清理 + // setTimeout(() => { + // Engine3D.views[this.viewIndex].graphic3D.Clear(name) + // }, lifeTime * 1000); + } + + private reportErrorWarning(warningString: string): void { + const warning = Ammo.UTF8ToString(warningString as unknown as number); + console.error(warning); + } + + private draw3dText(location: Ammo.btVector3, textString: string): void { + const _location = Ammo.wrapPointer(location as unknown as number, Ammo.btVector3); + const _textString = Ammo.UTF8ToString(textString as unknown as number); + console.log("draw3dText", _location, _textString); + } + + private clearLines(): void { + // let view = Engine3D.views[this.viewIndex]; + // this.lineNameList.forEach(name => view.graphic3D.Clear(name)); + this.lineNameList.forEach(name => this.graphic3D.Clear(name)); + this.lineNameList.length = 0; + } +} diff --git a/samples/physics/Sample_Dominoes.ts b/samples/physics/Sample_Dominoes.ts new file mode 100644 index 00000000..6cc8bc27 --- /dev/null +++ b/samples/physics/Sample_Dominoes.ts @@ -0,0 +1,170 @@ +import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Color, Quaternion, GridObject } from "@orillusion/core"; +import { CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics"; +import { Stats } from "@orillusion/stats"; +import dat from "dat.gui"; +import { Graphic3D } from '@orillusion/graphic' + +/** + * Sample class demonstrating the creation of a domino effect with physics interactions. + */ +class Sample_Dominoes { + scene: Scene3D; + gui: dat.GUI; + + async run() { + // init physics and engine + await Physics.init(); + await Engine3D.init({ renderLoop: () => Physics.update() }); + Engine3D.setting.shadow.updateFrameRate = 1; + Engine3D.setting.shadow.shadowSize = 2048; + Engine3D.setting.shadow.shadowBound = 200; + + let scene = this.scene = new Scene3D(); + scene.addComponent(Stats); + + // 启用物理调试功能时,需要为绘制器传入graphic3D对象 + const graphic3D = new Graphic3D(); + scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D, { enable: false }); + + this.gui = new dat.GUI(); + let f = this.gui.addFolder('PhysicsDebug'); + f.add(Physics.debugDrawer, 'enable'); + f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + f.add(Physics, 'isStop'); + f.open(); + + let camera = CameraUtil.createCamera3DObject(scene); + camera.perspective(60, Engine3D.aspect, 0.1, 800.0); + camera.object3D.addComponent(HoverCameraController).setCamera(0, -25, 100); + + // Create directional light + let lightObj3D = new Object3D(); + lightObj3D.localRotation = new Vector3(120, 130, 50); + lightObj3D.addComponent(DirectLight).castShadow = true; + scene.addChild(lightObj3D); + + // init sky + scene.addComponent(AtmosphericComponent).sunY = 0.6; + + let view = new View3D(); + view.camera = camera; + view.scene = scene; + + Engine3D.startRenderView(view); + + await this.initScene(); + } + + // init the scene with ground, slide, ball, and dominoes. + private async initScene() { + // Create ground and add rigidbody + let ground = Object3DUtil.GetPlane(Engine3D.res.whiteTexture); + ground.scaleX = ground.scaleY = ground.scaleZ = 200; + + this.scene.addChild(ground); + + let rigidbody = ground.addComponent(Rigidbody); + rigidbody.shape = CollisionShapeUtil.createStaticPlaneShape(); // Static plane shape at origin, extending infinitely upwards + rigidbody.mass = 0; + rigidbody.friction = 100; // Set high friction for the ground + rigidbody.isSilent = true; // Disable collision events + + + // Create dominoes + this.createDominoes(); + + // init slide + await this.initSlide(); + + // Create ball + this.createBall(); + + } + + // Load and initialize the slide model. + private async initSlide() { + let model = await Engine3D.res.loadGltf('https://raw.githubusercontent.com/ID-Emmett/static-assets/main/models/slide.glb'); + model.x = -40; + this.scene.addChild(model); + + let rigidbody = model.addComponent(Rigidbody); + rigidbody.shape = Rigidbody.collisionShape.createBvhTriangleMeshShape(model); + rigidbody.mass = 0; + rigidbody.friction = 0.1; + // Disable debug visibility for the physics shape + rigidbody.isDisableDebugVisible = true; + this.gui.__folders['PhysicsDebug'].add(rigidbody, 'isDisableDebugVisible').listen(); + } + + // Create a series of dominoes with rigid bodies and arrange them in an S-shaped curve. + private createDominoes() { + const width = 0.5; + const height = 5; + const depth = 2; + + const originX = -7; + const originZ = 4.7; + + const totalDominoes = 40; + const segmentLength = 2; // Distance between dominoes + + let previousX = originX; + let previousZ = originZ; + + for (let i = 0; i < totalDominoes; i++) { + let box = Object3DUtil.GetSingleCube(width, height, depth, Math.random(), Math.random(), Math.random()); + + let angle = (Math.PI / (totalDominoes / 2)) * i; + let x = originX + segmentLength * i; + let z = originZ + Math.sin(angle) * 15; // Adjust sine curve amplitude for S-shape + + box.localPosition = new Vector3(x, height / 2, z); + + // Adjust each domino's rotation to align with the curve + let deltaX = x - previousX; + let deltaZ = z - previousZ; + box.rotationY = i === 0 ? -48 : -Math.atan2(deltaZ, deltaX) * (180 / Math.PI); + + this.scene.addChild(box); + + previousX = x; + previousZ = z; + + let rigidbody = box.addComponent(Rigidbody); + rigidbody.shape = Rigidbody.collisionShape.createBoxShape(box); + rigidbody.mass = 30; + rigidbody.friction = 0.1; + rigidbody.collisionEvent = (contactPoint, selfBody, otherBody) => { + rigidbody.enableCollisionEvent = false; // Handle collision only once + (box.getComponent(MeshRenderer).material as LitMaterial).baseColor = Color.random(); + }; + } + } + + // Create a ball with a rigid body. + private createBall() { + let ball = Object3DUtil.GetSingleSphere(0.8, Math.random(), Math.random(), Math.random()); + + const originPos = new Vector3(-13.2 - 40, 28.6, 6.2); + ball.localPosition = originPos; + let rigidbody = ball.addComponent(Rigidbody); + rigidbody.shape = Rigidbody.collisionShape.createSphereShape(ball); + rigidbody.mass = 50; + rigidbody.enablePhysicsTransformSync = true; + rigidbody.friction = 0.05; + + let f = this.gui.addFolder("ball"); + f.open(); + f.add(rigidbody, 'isKinematic').onChange(v => v || (rigidbody.enablePhysicsTransformSync = true)); + f.add({ SyncInfo: "Modify XYZ to sync rigidbody" }, "SyncInfo"); + f.add(ball.transform, 'x', -100, 100, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); + f.add(ball.transform, 'y', 0.8, 40, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); + f.add(ball.transform, 'z', -100, 100, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); + f.add({ ResetPosition: () => rigidbody.updateTransform(originPos, Quaternion._zero, true) }, 'ResetPosition'); + + this.scene.addChild(ball); + } +} + +new Sample_Dominoes().run(); diff --git a/samples/physics/Sample_EatTheBox.ts b/samples/physics/Sample_EatTheBox.ts index 5d7fae43..f60bb957 100644 --- a/samples/physics/Sample_EatTheBox.ts +++ b/samples/physics/Sample_EatTheBox.ts @@ -116,21 +116,12 @@ class Sample_EatTheBox { rigidbody.rollingFriction = 10; rigidbody1.restitution = rigidbody2.restitution = rigidbody3.restitution = rigidbody4.restitution = 0.3; //set their index to -1 - rigidbody.addInitedFunction(() => { - rigidbody.btRigidbody.setUserIndex(-1); - }, this); - rigidbody1.addInitedFunction(() => { - rigidbody1.btRigidbody.setUserIndex(-1); - }, this); - rigidbody2.addInitedFunction(() => { - rigidbody2.btRigidbody.setUserIndex(-1); - }, this); - rigidbody3.addInitedFunction(() => { - rigidbody3.btRigidbody.setUserIndex(-1); - }, this); - rigidbody4.addInitedFunction(() => { - rigidbody4.btRigidbody.setUserIndex(-1); - }, this); + rigidbody.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); + rigidbody1.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); + rigidbody2.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); + rigidbody3.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); + rigidbody4.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); + this.view.scene.addChild(floor); this.view.scene.addChild(border1); this.view.scene.addChild(border2); @@ -156,15 +147,13 @@ class Sample_EatTheBox { rig.mass = 0; let col = boxObj.addComponent(ColliderComponent); col.shape = boxColliderShape; - rig.addInitedFunction(() => { - //get original rigidbody to get/set more property - let btrig = rig.btRigidbody; + rig.wait().then(btRigidbody => { //set this colider as trigger,trigger will not respond to collision - btrig.setCollisionFlags(4); + btRigidbody.setCollisionFlags(4); //set index to 0~9 - btrig.setUserIndex(index); + btRigidbody.setUserIndex(index); this.foods[index] = boxObj; - }, this); + }); boxObj.addComponent(RotateScript); this.view.scene.addChild(boxObj); } @@ -181,9 +170,7 @@ class Sample_EatTheBox { //add movescript this.moveScript = sphereObj.addComponent(MoveScript); this.moveScript.rigidbody = sphereObj.addComponent(Rigidbody); - this.moveScript.rigidbody.addInitedFunction(() => { - this.moveScript.rigidbody.btRigidbody.setUserIndex(-1); - }, this); + this.moveScript.rigidbody.wait().then(btRigidbody => btRigidbody.setUserIndex(-1)); this.moveScript.rigidbody.mass = 10; let collider = sphereObj.addComponent(ColliderComponent); collider.shape = new SphereColliderShape(1); diff --git a/samples/physics/Sample_MultipleConstraints.ts b/samples/physics/Sample_MultipleConstraints.ts new file mode 100644 index 00000000..25183033 --- /dev/null +++ b/samples/physics/Sample_MultipleConstraints.ts @@ -0,0 +1,324 @@ +import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, BitmapTexture2D, UnLitMaterial, PlaneGeometry, GPUCullMode, Quaternion, Color } from "@orillusion/core"; +import { Stats } from "@orillusion/stats"; +import { ActivationState, ClothSoftbody, CollisionShapeUtil, DebugDrawMode, FixedConstraint, Generic6DofSpringConstraint, HingeConstraint, Physics, PointToPointConstraint, Rigidbody, SliderConstraint } from "@orillusion/physics"; +import dat from "dat.gui"; +import { Graphic3D } from "@orillusion/graphic"; + +/** + * Sample class demonstrating the use of multiple constraints in a physics simulation. + */ +class Sample_MultipleConstraints { + scene: Scene3D; + gui: dat.GUI; + + async run() { + // init physics and engine + await Physics.init({ useSoftBody: true }); + await Engine3D.init({ renderLoop: () => Physics.update() }); + + this.gui = new dat.GUI(); + + this.scene = new Scene3D(); + this.scene.addComponent(Stats); + + // 在引擎启动后初始化物理调试功能,需要为绘制器传入 graphic3D 对象 + const graphic3D = new Graphic3D(); + this.scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D, { + enable: false, + debugDrawMode: DebugDrawMode.DrawConstraintLimits + }) + + let camera = CameraUtil.createCamera3DObject(this.scene); + camera.perspective(60, Engine3D.aspect, 0.1, 800.0); + camera.object3D.addComponent(HoverCameraController).setCamera(60, -25, 50); + + // create directional light + let light = new Object3D(); + light.localRotation = new Vector3(36, -130, 60); + let dl = light.addComponent(DirectLight) + dl.castShadow = true; + dl.intensity = 3; + this.scene.addChild(light); + + // init sky + this.scene.addComponent(AtmosphericComponent).sunY = 0.6; + + let view = new View3D(); + view.camera = camera; + view.scene = this.scene; + + this.physicsDebug() + + Engine3D.startRenderView(view); + + // Create ground, impactor, turntable, and chains + this.createGround(); + await this.createImpactor(); + await this.createTurntable(); + await this.createChains(); + + } + + private physicsDebug() { + let physicsFolder = this.gui.addFolder('PhysicsDebug'); + physicsFolder.add(Physics.debugDrawer, 'enable'); + physicsFolder.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + physicsFolder.add(Physics, 'isStop'); + physicsFolder.open(); + } + + private async createGround() { + // Create ground + let ground = Object3DUtil.GetSingleCube(61, 2, 20, 1, 1, 1); + ground.y = -1; // Set ground half-height + this.scene.addChild(ground); + + // Add rigidbody to ground + let groundRb = ground.addComponent(Rigidbody); + groundRb.shape = CollisionShapeUtil.createStaticPlaneShape(Vector3.UP, 1); + groundRb.mass = 0; + } + + private async createImpactor() { + // Create shelves + const shelfSize = 0.5; + const shelfHeight = 5; + + let shelfLeft = Object3DUtil.GetCube(); + shelfLeft.localScale = new Vector3(shelfSize, shelfHeight, shelfSize); + shelfLeft.localPosition = new Vector3(-30, shelfHeight / 2, 0); + + let shelfRight = shelfLeft.clone(); + shelfRight.localPosition = new Vector3(30, shelfHeight / 2, 0); + + let shelfTop = Object3DUtil.GetCube(); + shelfTop.localScale = new Vector3(60 - shelfSize, shelfSize, shelfSize); + shelfTop.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 0); + + // Add rigidbodies to shelves + let shelfRightRb = this.addBoxShapeRigidBody(shelfRight, 0); + let shelfLeftRb = this.addBoxShapeRigidBody(shelfLeft, 0); + this.addBoxShapeRigidBody(shelfTop, 0); + + this.scene.addChild(shelfLeft); + this.scene.addChild(shelfRight); + this.scene.addChild(shelfTop); + + // Create slider + let slider = Object3DUtil.GetSingleCube(4, 1, 1, Math.random(), Math.random(), Math.random()); + this.scene.addChild(slider); + + // Add rigidbody to slider + let sliderRb = this.addBoxShapeRigidBody(slider, 500, true, [0.2, 0]); + + // Create fulcrum + let fulcrum = Object3DUtil.GetCube(); + fulcrum.localScale = new Vector3(1, 1, 5); + fulcrum.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 3); + this.scene.addChild(fulcrum); + + // Add rigidbody to fulcrum and initialize cloth softbody + let fulcrumRb = this.addBoxShapeRigidBody(fulcrum, 200, true); + this.initClothSoftBody(fulcrumRb); + + // Create fixed constraint to attach slider to fulcrum + let fixedConstraint = slider.addComponent(FixedConstraint); + fixedConstraint.targetRigidbody = fulcrumRb; + fixedConstraint.pivotTarget = new Vector3(0, 0, -3); + + // Create slider constraint + let sliderConstraint = shelfTop.addComponent(SliderConstraint); + sliderConstraint.targetRigidbody = sliderRb; + sliderConstraint.lowerLinLimit = -30; + sliderConstraint.upperLinLimit = 30; + sliderConstraint.lowerAngLimit = 0; + sliderConstraint.upperAngLimit = 0; + sliderConstraint.poweredLinMotor = true; + sliderConstraint.maxLinMotorForce = 1; + sliderConstraint.targetLinMotorVelocity = 20; + + // Setup slider motor event controller + this.sliderMotorEventController(shelfLeftRb, shelfRightRb, sliderConstraint); + } + + private sliderMotorEventController(leftRb: Rigidbody, rightRb: Rigidbody, slider: SliderConstraint) { + // Control slider movement based on collision events + const timer = { pauseDuration: 1000 }; + + leftRb.collisionEvent = () => { + rightRb.enableCollisionEvent = true; + leftRb.enableCollisionEvent = false; + setTimeout(() => { + slider.targetLinMotorVelocity = Math.abs(slider.targetLinMotorVelocity); + setTimeout(() => leftRb.enableCollisionEvent = true, 1000); + }, timer.pauseDuration); + }; + + rightRb.collisionEvent = () => { + rightRb.enableCollisionEvent = false; + leftRb.enableCollisionEvent = true; + setTimeout(() => { + slider.targetLinMotorVelocity = -Math.abs(slider.targetLinMotorVelocity); + setTimeout(() => rightRb.enableCollisionEvent = true, 1000); + }, timer.pauseDuration); + }; + + // GUI controls for slider motor + let folder = this.gui.addFolder('Slider Motor Controller'); + folder.open(); + folder.add(slider, 'poweredLinMotor'); + folder.add(slider, 'maxLinMotorForce', 0, 30, 1); + folder.add({ velocity: slider.targetLinMotorVelocity }, 'velocity', 0, 30, 1).onChange(v => { + slider.targetLinMotorVelocity = slider.targetLinMotorVelocity > 0 ? v : -v; + }); + folder.add(timer, 'pauseDuration', 0, 3000, 1000); + } + + private async createTurntable() { + // Create turntable components + const columnWidth = 0.5; + const columnHeight = 4.75 - columnWidth / 2; + const columnDepth = 0.5; + + let column = Object3DUtil.GetCube(); + column.localScale = new Vector3(columnWidth, columnHeight, columnDepth); + column.localPosition = new Vector3(0, columnHeight / 2, 8); + + let arm1 = Object3DUtil.GetCube(); + arm1.localScale = new Vector3(10, 0.5, 0.5); + arm1.localPosition = new Vector3(0, columnHeight + columnWidth / 2, 8); + + let arm2 = arm1.clone(); + arm2.y += 10; // Ensure no overlap before adding constraints + arm2.rotationY = 45; + + this.scene.addChild(column); + this.scene.addChild(arm1); + this.scene.addChild(arm2); + + // Add rigidbodies to turntable components + this.addBoxShapeRigidBody(column, 0); + let arm1Rb = this.addBoxShapeRigidBody(arm1, 500, true); + let arm2Rb = this.addBoxShapeRigidBody(arm2, 500, true); + + // Create hinge constraint to attach arm1 to column + let hinge = column.addComponent(HingeConstraint); + hinge.targetRigidbody = arm1Rb; + hinge.pivotSelf.set(0, columnHeight / 2 + columnWidth / 2, 0); + hinge.enableAngularMotor(true, 5, 50); + + // Create fixed constraint to attach arm2 to arm1 + let fixedConstraint = arm2.addComponent(FixedConstraint); + fixedConstraint.targetRigidbody = arm1Rb; + fixedConstraint.rotationTarget.fromEulerAngles(0, 90, 0); + fixedConstraint.pivotTarget.set(0, 0, 0); + } + + private async createChains() { + const chainHeight = 1; + + let chainLink = Object3DUtil.GetCube(); + chainLink.localScale = new Vector3(0.25, chainHeight, 0.25); + chainLink.localPosition = new Vector3(5, 16, 5); + this.scene.addChild(chainLink); + + // Add static rigidbody to the first chain link + let chainRb = this.addBoxShapeRigidBody(chainLink, 0); + let prevRb = chainRb; + + // Create chain links and add point-to-point constraints + for (let i = 0; i < 10; i++) { + let link = chainLink.clone(); + link.y -= (i + 1) * chainHeight; + this.scene.addChild(link); + + let linkRb = this.addBoxShapeRigidBody(link, 1, false, [0.3, 0.3]); + linkRb.isSilent = true; // Disable collision events + + let p2p = link.addComponent(PointToPointConstraint); + p2p.targetRigidbody = prevRb; + p2p.pivotTarget.y = -chainHeight / 2; + p2p.pivotSelf.y = chainHeight / 2; + + prevRb = linkRb; + } + + // Create a sphere and add point-to-point constraint to the last chain link + const sphereRadius = 0.8; + let sphere = Object3DUtil.GetSingleSphere(sphereRadius, 1, 1, 1); + let sphereMaterial = (sphere.getComponent(MeshRenderer).material as LitMaterial); + + sphere.localPosition = new Vector3(5, 4.5, 5); + this.scene.addChild(sphere); + + let sphereRb = sphere.addComponent(Rigidbody); + sphereRb.shape = CollisionShapeUtil.createSphereShape(sphere); + sphereRb.mass = 2; + sphereRb.damping = [0.3, 0.3]; + sphereRb.enablePhysicsTransformSync = true; + + // Sphere collision event to change color + let timer: number | null = null; + sphereRb.collisionEvent = () => { + if (timer !== null) clearTimeout(timer); + else sphereMaterial.baseColor = new Color(Color.SALMON); + + timer = setTimeout(() => { + sphereMaterial.baseColor = Color.COLOR_WHITE; + timer = null; + }, 1000); + }; + + let p2p = sphere.addComponent(PointToPointConstraint); + p2p.disableCollisionsBetweenLinkedBodies = true; + p2p.targetRigidbody = prevRb; + p2p.pivotTarget.y = -chainHeight / 2; + p2p.pivotSelf.y = sphereRadius; + } + + private async initClothSoftBody(anchorRb: Rigidbody) { + const cloth = new Object3D(); + let meshRenderer = cloth.addComponent(MeshRenderer); + meshRenderer.geometry = new PlaneGeometry(3, 3, 10, 10); + let material = new LitMaterial(); + material.baseMap = Engine3D.res.redTexture; + material.cullMode = GPUCullMode.none; + meshRenderer.material = material; + + this.scene.addChild(cloth); + + // Add cloth softbody component + let softBody = cloth.addComponent(ClothSoftbody); + softBody.mass = 5; + softBody.margin = 0.1; + softBody.anchorRigidbody = anchorRb; // Anchor rigidbody + softBody.anchorIndices = ['leftTop', 'top', 'rightTop']; // Anchor points + softBody.influence = 1; // Attachment influence + softBody.disableCollision = false; // Enable collision with anchor + softBody.applyPosition = new Vector3(0, -2.1, 0); // Relative position to anchor + softBody.applyRotation = new Vector3(0, 90, 0); // Relative rotation to anchor + + // Configure softbody parameters + softBody.wait().then(btSoftbody => { + let sbConfig = btSoftbody.get_m_cfg(); + sbConfig.set_kDF(0.2); + sbConfig.set_kDP(0.01); + sbConfig.set_kLF(0.02); + sbConfig.set_kDG(0.001); + }); + } + + private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean, damping?: [number, number]) { + let rigidbody = obj.addComponent(Rigidbody); + rigidbody.shape = CollisionShapeUtil.createBoxShape(obj); + rigidbody.mass = mass; + + if (disableHibernation) rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION; + if (damping) rigidbody.damping = damping; + + return rigidbody; + } +} + +new Sample_MultipleConstraints().run(); diff --git a/samples/physics/Sample_MultipleShapes.ts b/samples/physics/Sample_MultipleShapes.ts new file mode 100644 index 00000000..653c3640 --- /dev/null +++ b/samples/physics/Sample_MultipleShapes.ts @@ -0,0 +1,308 @@ +import { Engine3D, LitMaterial, MeshRenderer, BoxGeometry, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, SphereGeometry, CameraUtil, HoverCameraController, BitmapTexture2D, VertexAttributeName, Color, CylinderGeometry, TorusGeometry, ComponentBase } from "@orillusion/core"; +import { TerrainGeometry } from "@orillusion/effect"; +import { Graphic3D } from "@orillusion/graphic"; +import { Ammo, CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics"; +import { Stats } from "@orillusion/stats"; +import dat from "dat.gui"; + +class Sample_MultipleShapes { + scene: Scene3D; + terrain: Object3D; + gui: dat.GUI; + + async run() { + // init physics and engine + await Physics.init(); + await Engine3D.init({ + renderLoop: () => Physics.update() + }); + + this.gui = new dat.GUI(); + + // shadow settings + Engine3D.setting.shadow.shadowBias = 0.01; + Engine3D.setting.shadow.shadowSize = 1024 * 4; + Engine3D.setting.shadow.csmMargin = 0.1; + Engine3D.setting.shadow.csmScatteringExp = 0.8; + Engine3D.setting.shadow.csmAreaScale = 0.1; + Engine3D.setting.shadow.updateFrameRate = 1; + + this.scene = new Scene3D(); + this.scene.addComponent(Stats); + + // 在引擎启动后初始化物理调试功能,需要为绘制器传入 graphic3D 对象 + const graphic3D = new Graphic3D(); + this.scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D, { + enable: false, + }) + + // Setup camera + let camera = CameraUtil.createCamera3DObject(this.scene); + camera.perspective(60, Engine3D.aspect, 0.1, 800.0); + camera.enableCSM = true; + + let hoverCtrl = camera.object3D.addComponent(HoverCameraController); + hoverCtrl.setCamera(0, -25, 100); + hoverCtrl.dragSmooth = 4; + + // Create directional light + let lightObj3D = new Object3D(); + lightObj3D.localRotation = new Vector3(-35, -143, 92); + + let light = lightObj3D.addComponent(DirectLight); + light.lightColor = Color.COLOR_WHITE; + light.castShadow = true; + light.intensity = 2.2; + this.scene.addChild(light.object3D); + + // init sky + let atmosphericSky = this.scene.addComponent(AtmosphericComponent); + atmosphericSky.sunY = 0.6; + + // Setup view + let view = new View3D(); + view.camera = camera; + view.scene = this.scene; + + Engine3D.startRenderView(view); + + this.setupPhysicsGUI(); + + // init terrain and create static planes + await this.initTerrain(); + this.createStaticPlanes(); + + this.scene.addComponent(BoxGenerator); + } + + async initTerrain() { + // Load textures + let bitmapTexture = await Engine3D.res.loadTexture('terrain/test01/bitmap.png'); + let heightTexture = await Engine3D.res.loadTexture('terrain/test01/height.png'); + + const width = 100; + const height = 100; + const terrainMaxHeight = 60; + const segment = 60 + + // Create terrain geometry + let terrainGeometry = new TerrainGeometry(width, height, segment, segment); + terrainGeometry.setHeight(heightTexture as BitmapTexture2D, terrainMaxHeight); + + let terrain = new Object3D(); + let mr = terrain.addComponent(MeshRenderer); + mr.geometry = terrainGeometry; + + let mat = new LitMaterial(); + mat.baseMap = bitmapTexture; + mat.metallic = 0; + mat.roughness = 1.3; + mr.material = mat; + + this.terrain = terrain; + this.scene.addChild(terrain); + + // Add rigidbody to terrain + let terrainRb = terrain.addComponent(Rigidbody); + terrainRb.shape = Rigidbody.collisionShape.createHeightfieldTerrainShape(terrain); + terrainRb.mass = 0; // Static rigidbody + terrainRb.margin = 0.05; + terrainRb.isDisableDebugVisible = true; + terrainRb.friction = 1; + + this.gui.__folders['PhysicsDebugDrawer'].add(terrainRb, 'isDisableDebugVisible').name('disableTerrain').listen(); + this.setupTerrainGUI(width, height, terrainMaxHeight); + } + + // Create static planes for boundaries + createStaticPlanes() { + // Create bottom static plane + let staticFloorBottom = Object3DUtil.GetPlane(Engine3D.res.whiteTexture); + staticFloorBottom.y = -500; + staticFloorBottom.transform.enable = false; + this.scene.addChild(staticFloorBottom); + + let bottomRb = staticFloorBottom.addComponent(Rigidbody); + bottomRb.shape = CollisionShapeUtil.createStaticPlaneShape(); + bottomRb.mass = 0; + + // Create top static plane + let staticFloorTop = Object3DUtil.GetPlane(Engine3D.res.whiteTexture); + staticFloorTop.y = 100; + staticFloorTop.transform.enable = false; + this.scene.addChild(staticFloorTop); + + let topRb = staticFloorTop.addComponent(Rigidbody); + topRb.shape = CollisionShapeUtil.createStaticPlaneShape(Vector3.DOWN); + topRb.mass = 0; + } + + setupPhysicsGUI() { + // Physics debug drawer settings + let debugDrawer = Physics.debugDrawer; + let f = this.gui.addFolder("PhysicsDebugDrawer"); + f.add(debugDrawer, 'enable').listen(); + f.add(debugDrawer, 'debugMode', debugDrawer.debugModeList); + f.add(debugDrawer, 'updateFreq', 1, 360, 1); + f.add(debugDrawer, 'maxLineCount', 100, 33000, 100); + f.open(); + + // General physics settings + let p = this.gui.addFolder("Physics"); + p.add(Physics, 'isStop'); + p.add(Physics.gravity, 'y', -20, 20, 0.1).name('gravity').onChange(() => { + Physics.gravity = Physics.gravity; + Physics.rigidBodyUtil.activateCollisionBodies(); + }); + p.open(); + } + + setupTerrainGUI(width: number, height: number, terrainMaxHeight: number) { + let terrainData = { + width, height, terrainMaxHeight + }; + + let f = this.gui.addFolder("terrain"); + f.add(terrainData, 'terrainMaxHeight', -100, 100, 1).name('terrainScale').onChange(v => setTerrainSize(v, 'terrainMaxHeight')).onFinishChange(v => updateShape()); + f.add(terrainData, 'width', 100, 200, 1).onChange(v => setTerrainSize(v, 'width')).onFinishChange(v => updateShape()); + f.add(terrainData, 'height', 100, 200, 1).onChange(v => setTerrainSize(v, 'height')).onFinishChange(v => updateShape()); + f.open(); + + const dimensionSpecs = { + width: { index: 0, value: terrainData.width }, + height: { index: 2, value: terrainData.height }, + terrainMaxHeight: { index: 1, value: terrainData.terrainMaxHeight } + }; + + const terrainGeometry = this.terrain.getComponent(MeshRenderer).geometry; + const setTerrainSize = (size: number, specs: 'width' | 'height' | 'terrainMaxHeight') => { + size ||= 0.01; // Avoid zero to prevent data loss + let posAttrData = terrainGeometry.getAttribute(VertexAttributeName.position); + let dimension = dimensionSpecs[specs]; + for (let i = 0, count = posAttrData.data.length / 3; i < count; i++) { + posAttrData.data[i * 3 + dimension.index] *= size / dimension.value; + } + dimension.value = size; + + if (specs !== 'terrainMaxHeight') terrainGeometry[specs] = size; + + terrainGeometry.vertexBuffer.upload(VertexAttributeName.position, posAttrData); + terrainGeometry.computeNormals(); + }; + + const terrainRb = this.terrain.getComponent(Rigidbody); + const updateShape = () => { + terrainRb.shape = Rigidbody.collisionShape.createHeightfieldTerrainShape(this.terrain); + Physics.rigidBodyUtil.activateCollisionBodies(); + }; + } +} + +class BoxGenerator extends ComponentBase { + private lastTime: number = performance.now(); // Save last time + + public container: Object3D; + public interval: number = 1000; // Interval for adding shapes + public totalShapes: number = 30; // Maximum number of shapes + + async start() { + this.container = new Object3D(); + this.object3D.addChild(this.container); + } + + // Update loop + public onUpdate(): void { + let now: number = performance.now(); + if (now - this.lastTime > this.interval) { + if (this.container.numChildren >= this.totalShapes) { + let index = Math.floor(now / this.interval) % this.totalShapes; + let shapeObject = this.container.getChildByIndex(index) as Object3D; + shapeObject.localPosition.set(Math.random() * 60 - 60 / 2, 40, Math.random() * 60 - 60 / 2); + shapeObject.getComponent(Rigidbody).updateTransform(shapeObject.localPosition, null, true); + } else { + this.addRandomShape(); + } + this.lastTime = now; // Save current time + } + } + + private addRandomShape(): void { + const shapeObject = new Object3D(); + let mr = shapeObject.addComponent(MeshRenderer); + let mat = new LitMaterial(); + mat.baseColor = Color.random(); + + let size = 1 + Math.random() / 2; + let height = 1 + Math.random() * (3 - 1); + let radius = 0.5 + Math.random() / 2; + const segments = 32; + + let shape: Ammo.btCollisionShape; + let shapeType = Math.floor(Math.random() * 6); // Six basic shapes + switch (shapeType) { + case 0: // Box shape + mr.geometry = new BoxGeometry(size, size, size); + mr.material = mat; + shape = CollisionShapeUtil.createBoxShape(shapeObject); + break; + case 1: // Sphere shape + mr.geometry = new SphereGeometry(radius, segments, segments); + mr.material = mat; + shape = CollisionShapeUtil.createSphereShape(shapeObject); + break; + case 2: // Cylinder shape + mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments); + mr.materials = [mat, mat, mat]; + shape = CollisionShapeUtil.createCylinderShape(shapeObject); + break; + case 3: // Cone shape + mr.geometry = new CylinderGeometry(0.01, radius, height, segments, segments); + mr.materials = [mat, mat, mat]; + shape = CollisionShapeUtil.createConeShape(shapeObject); + break; + case 4: // Capsule shape + mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments); + mr.material = mat; + const { r, g, b } = mat.baseColor; + let topSphere = Object3DUtil.GetSingleSphere(radius, r, g, b); + topSphere.y = height / 2; + let bottomSphere = topSphere.clone(); + bottomSphere.y = -height / 2; + shapeObject.addChild(topSphere); + shapeObject.addChild(bottomSphere); + shape = CollisionShapeUtil.createCapsuleShape(shapeObject); + break; + case 5: // Torus shape (convex hull shape) + mr.geometry = new TorusGeometry(radius, size / 5, segments / 2, segments / 2); + mr.material = mat; + shape = CollisionShapeUtil.createConvexHullShape(shapeObject); + break; + default: + break; + } + + const posRange = 60; + shapeObject.x = Math.random() * posRange - posRange / 2; + shapeObject.y = 40; + shapeObject.z = Math.random() * posRange - posRange / 2; + + shapeObject.localRotation = new Vector3(Math.random() * 360, Math.random() * 360, Math.random() * 360); + this.container.addChild(shapeObject); + + // Add rigidbody to shape + let rigidbody = shapeObject.addComponent(Rigidbody); + rigidbody.shape = shape; + rigidbody.mass = Math.random() * 10 + 0.1; + rigidbody.rollingFriction = 0.5; + rigidbody.damping = [0.1, 0.1]; + + // Enable continuous collision detection (CCD) + const maxDimension = Math.max(size, height, radius); + const ccdMotionThreshold = maxDimension * 0.1; // Set motion threshold to 10% of max dimension + const ccdSweptSphereRadius = maxDimension * 0.05; // Set swept sphere radius to 5% of max dimension + rigidbody.ccdSettings = [ccdMotionThreshold, ccdSweptSphereRadius]; + } +} + +new Sample_MultipleShapes().run(); diff --git a/samples/physics/Sample_PhysicsCar.ts b/samples/physics/Sample_PhysicsCar.ts index b17337fa..7f4b1537 100644 --- a/samples/physics/Sample_PhysicsCar.ts +++ b/samples/physics/Sample_PhysicsCar.ts @@ -131,7 +131,7 @@ class VehicleKeyboardController extends ComponentBase { protected mEngineForce = 0; protected mBreakingForce = 0; protected mVehicleSteering = 0; - protected mAmmoVehicle; + protected mAmmoVehicle: Ammo.btRaycastVehicle; protected mVehicleArgs = { bodyMass: 800, friction: 1000, @@ -235,7 +235,8 @@ class VehicleKeyboardController extends ComponentBase { Engine3D.inputSystem.addEventListener(KeyEvent.KEY_UP, this.onKeyUp, this); Engine3D.inputSystem.addEventListener(KeyEvent.KEY_DOWN, this.onKeyDown, this); } - onUpdate() { + // onUpdate() { + onLateUpdate() { if (!this.mAmmoVehicle) return; const vehicle = this.mAmmoVehicle; const speed = vehicle.getCurrentSpeedKmHour(); @@ -299,7 +300,12 @@ class VehicleKeyboardController extends ComponentBase { } // update body position let tm, p, q, qua = Quaternion.HELP_0; - tm = vehicle.getChassisWorldTransform(); + // tm = vehicle.getChassisWorldTransform(); + + // Use an interpolation transform + vehicle.getRigidBody().getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + tm = Physics.TEMP_TRANSFORM; + p = tm.getOrigin(); this.mBody.x = p.x() this.mBody.y = p.y() @@ -367,7 +373,7 @@ class fixedCameraController extends ComponentBase { set target(obj) { this._target = obj; } - onUpdate() { + onBeforeUpdate() { if (!this._target) return; this._tempDir.set(0, 0, -1); const q = Quaternion.HELP_0; diff --git a/samples/physics/Sample_ShootTheBox.ts b/samples/physics/Sample_ShootTheBox.ts index c2302daf..615c9d63 100644 --- a/samples/physics/Sample_ShootTheBox.ts +++ b/samples/physics/Sample_ShootTheBox.ts @@ -43,7 +43,7 @@ class Sample_ShootTheBox { //add DirectLight let lightObj = new Object3D(); let light = lightObj.addComponent(DirectLight); - light.intensity = 8; + light.intensity = 4; light.castShadow = true; lightObj.rotationX = 60; lightObj.rotationY = 140; @@ -129,9 +129,9 @@ class Sample_ShootTheBox { let rigidBody = ball.addComponent(Rigidbody); rigidBody.mass = 10; //set velocity after rigidbody inited - rigidBody.addInitedFunction(() => { - rigidBody.velocity = ray.direction.multiplyScalar(10000 * this.ballSpeed); - }, this); + rigidBody.wait().then(() => { + rigidBody.linearVelocity = ray.direction.multiplyScalar(20 * this.ballSpeed); + }); ball.transform.localPosition = ray.origin; this.view.scene.addChild(ball); } diff --git a/samples/physics/Sample_dofSpringConstraint.ts b/samples/physics/Sample_dofSpringConstraint.ts new file mode 100644 index 00000000..2fe769f6 --- /dev/null +++ b/samples/physics/Sample_dofSpringConstraint.ts @@ -0,0 +1,225 @@ +import { Engine3D, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Quaternion, GridObject } from "@orillusion/core"; +import { Stats } from "@orillusion/stats"; +import { ActivationState, CollisionShapeUtil, DebugDrawMode, Generic6DofSpringConstraint, Physics, Rigidbody } from "@orillusion/physics"; +import dat from "dat.gui"; +import { Graphic3D } from "@orillusion/graphic"; + +/** + * Sample class demonstrating the use of multiple constraints in a physics simulation. + */ +class Sample_MultipleConstraints { + scene: Scene3D; + gui: dat.GUI; + + async run() { + // Initialize physics and engine + await Physics.init(); + await Engine3D.init({ renderLoop: () => Physics.update() }); + + let scene = this.scene = new Scene3D(); + scene.addComponent(Stats); + + // 在引擎启动后初始化物理调试功能,需要为绘制器传入 graphic3D 对象 + const graphic3D = new Graphic3D(); + scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D, { + enable: false, + debugDrawMode: DebugDrawMode.DrawConstraintLimits + }) + + this.gui = new dat.GUI(); + let f = this.gui.addFolder('PhysicsDebug'); + f.add(Physics.debugDrawer, 'enable'); + f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + f.open(); + + let camera = CameraUtil.createCamera3DObject(scene); + camera.perspective(60, Engine3D.aspect, 0.1, 800.0); + camera.object3D.addComponent(HoverCameraController).setCamera(140, -25, 20, new Vector3(8, 4, 0)); + + // Create directional light + let lightObj3D = new Object3D(); + lightObj3D.localRotation = new Vector3(36, -130, 60); + lightObj3D.addComponent(DirectLight).castShadow = true; + scene.addChild(lightObj3D); + + // Initialize sky + scene.addComponent(AtmosphericComponent).sunY = 0.6; + + let view = new View3D(); + view.camera = camera; + view.scene = scene; + + Engine3D.startRenderView(view); + + // Create ground, bridge, and ball + this.createGround(); + this.createBridge(); + this.createBall(); + } + + //Create the ground plane. + private async createGround() { + let ground = Object3DUtil.GetPlane(Engine3D.res.whiteTexture); + ground.scaleX = 50; + ground.scaleZ = 50; + this.scene.addChild(ground); + + let rigidbody = ground.addComponent(Rigidbody); + rigidbody.shape = CollisionShapeUtil.createStaticPlaneShape(); + rigidbody.mass = 0; + } + + // Create a ball with a rigid body. + private createBall() { + let ball = Object3DUtil.GetSingleSphere(1, 1, 1, 1); + ball.localPosition = new Vector3(2, 10, 0); + this.scene.addChild(ball); + + let ballRb = ball.addComponent(Rigidbody); + ballRb.shape = CollisionShapeUtil.createSphereShape(ball); + ballRb.mass = 50; + ballRb.restitution = 1.2; + + let f = this.gui.addFolder('ball'); + f.add({ + ResetPosition: () => { + let pos = new Vector3(Math.random() * 15, 10, 0); + ballRb.updateTransform(pos, Quaternion._zero, true); + } + }, 'ResetPosition'); + f.open(); + } + + // Create a bridge using multiple segments and constraints. + private createBridge() { + const numSegments = 15; + const segmentWidth = 1; + const segmentHeight = 0.2; + const segmentDepth = 5; + const distance = 0.1; // Distance between bridge segments + const pierHeight = 5; // Height of the piers + + let bridgeSegments: Rigidbody[] = []; + for (let i = 0; i < numSegments; i++) { + const isStatic = i === 0 || i === numSegments - 1; + const mass = isStatic ? 0 : 2; + const staticHeight = isStatic ? pierHeight : 0; + let bridgeObj = Object3DUtil.GetSingleCube(segmentWidth, segmentHeight + staticHeight, segmentDepth, Math.random(), Math.random(), Math.random()); + + const posX = i * segmentWidth + i * distance || distance; + const posY = isStatic ? pierHeight / 2 + segmentHeight / 2 : pierHeight; + bridgeObj.localPosition = new Vector3(posX, posY, 0); + + this.scene.addChild(bridgeObj); + let segment = this.addBoxShapeRigidBody(bridgeObj, mass, !isStatic); + bridgeSegments.push(segment); + } + + let constraintList: Generic6DofSpringConstraint[] = []; + for (let i = 0; i < numSegments - 1; i++) { + let segmentA = bridgeSegments[i]; + let segmentB = bridgeSegments[i + 1]; + + let dofSpringConstraint = segmentA.object3D.addComponent(Generic6DofSpringConstraint); + dofSpringConstraint.targetRigidbody = segmentB; + + let selfHeight = i === 0 ? pierHeight / 2 : 0; // Start + let targetHeight = i === numSegments - 2 ? pierHeight / 2 : 0; // End + + dofSpringConstraint.pivotSelf.set(segmentWidth / 2, selfHeight, 0); + dofSpringConstraint.pivotTarget.set(-segmentWidth / 2, targetHeight, 0); + + dofSpringConstraint.linearLowerLimit.set(-distance, 0, 0); + dofSpringConstraint.linearUpperLimit.set(distance, 0, 0); + dofSpringConstraint.angularLowerLimit.set(0, -0.03, -Math.PI / 2); + dofSpringConstraint.angularUpperLimit.set(0, 0.03, Math.PI / 2); + + // Enable angular spring and configure parameters + for (let j = 3; j < 6; j++) { + dofSpringConstraint.enableSpring(j, true); + dofSpringConstraint.setStiffness(j, 10.0); + dofSpringConstraint.setDamping(j, 0.5); + dofSpringConstraint.setEquilibriumPoint(j); + } + + constraintList.push(dofSpringConstraint); + } + + this.debug(constraintList, distance); + } + + // Add a rigid body with a box shape to an object. + private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean) { + let rigidbody = obj.addComponent(Rigidbody); + rigidbody.shape = CollisionShapeUtil.createBoxShape(obj); + rigidbody.mass = mass; + if (disableHibernation) rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION; + return rigidbody; + } + + // Debug constraints using the dat.GUI interface. + private debug(constraintList: Generic6DofSpringConstraint[], distance: number) { + let f = this.gui.addFolder('Constraint'); + let refer = constraintList[0]; + + const spring = { + stiffness: 10.0, + damping: 0.5 + }; + f.add(spring, 'stiffness', 0, 100, 0.1).onChange(() => updateSpring()).listen(); + f.add(spring, 'damping', 0, 100, 0.1).onChange(() => updateSpring()).listen(); + + const updateSpring = () => { + constraintList.forEach(constraint => { + for (let j = 0; j < 6; j++) { + constraint.enableSpring(j, true); + constraint.setStiffness(j, spring.stiffness); + constraint.setDamping(j, spring.damping); + } + constraint.setEquilibriumPoint(); + }); + }; + + f.add({ angularLower: "angularLowerLimit" }, "angularLower"); + f.add(refer.angularLowerLimit, 'x', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen(); + f.add(refer.angularLowerLimit, 'y', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen(); + f.add(refer.angularLowerLimit, 'z', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen(); + + f.add({ angularUpper: "angularUpperLimit" }, "angularUpper"); + f.add(refer.angularUpperLimit, 'x', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen(); + f.add(refer.angularUpperLimit, 'y', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen(); + f.add(refer.angularUpperLimit, 'z', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen(); + + f.add({ linearLower: "linearLowerLimit" }, "linearLower"); + f.add(refer.linearLowerLimit, 'x', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen(); + f.add(refer.linearLowerLimit, 'y', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen(); + f.add(refer.linearLowerLimit, 'z', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen(); + + f.add({ linearUpper: "linearUpperLimit" }, "linearUpper"); + f.add(refer.linearUpperLimit, 'x', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen(); + f.add(refer.linearUpperLimit, 'y', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen(); + f.add(refer.linearUpperLimit, 'z', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen(); + + f.add({ + Reset: () => { + constraintList.forEach(constraint => { + constraint.linearLowerLimit = new Vector3(-distance, 0, 0); + constraint.linearUpperLimit = new Vector3(distance, 0, 0); + constraint.angularLowerLimit = new Vector3(0, -0.03, -Math.PI / 2); + constraint.angularUpperLimit = new Vector3(0, 0.03, Math.PI / 2); + }); + + spring['stiffness'] = 10.0; + spring['damping'] = 0.5; + updateSpring(); + } + }, 'Reset'); + + const updateLimit = (key: string) => { + constraintList.forEach(constraint => constraint[key] = refer[key]); + }; + } +} + +new Sample_MultipleConstraints().run(); diff --git a/src/util/GridObject.ts b/src/util/GridObject.ts index 43f89f9d..4e720e81 100644 --- a/src/util/GridObject.ts +++ b/src/util/GridObject.ts @@ -91,7 +91,6 @@ export class GridObject extends Object3D { let mr = z.addComponent(MeshRenderer); mr.geometry = line; let mat = mr.material = new UnLitMaterial(); - console.log(mat) mat.baseColor = new Color(0, 1, 0, 0.5); mat.blendMode = BlendMode.ADD; mat.castReflection = false; From 069e6d40d4510be09dfe3c7af9ac1b97bb855ccd Mon Sep 17 00:00:00 2001 From: Codeboy Date: Wed, 14 Aug 2024 07:18:51 +0800 Subject: [PATCH 16/25] feat(geometry): add extra geometry package, extrude geometry and text geometry (#442) Co-authored-by: ShuangLiu --- .../Curve/CubicBezierCurve2D.ts | 58 + .../geometry/ExtrudeGeometry/Curve/Curve2D.ts | 30 + .../ExtrudeGeometry/Curve/LineCurve2D.ts | 47 + .../Curve/QuadraticBezierCurve2D.ts | 51 + .../ExtrudeGeometry/ExtrudeGeometry.ts | 431 + packages/geometry/ExtrudeGeometry/Path2D.ts | 107 + packages/geometry/ExtrudeGeometry/Shape2D.ts | 26 + .../geometry/ExtrudeGeometry/ShapeUtils.ts | 57 + packages/geometry/TextGeometry.ts | 75 + packages/geometry/index.ts | 10 + packages/geometry/lib/opentype.d.ts | 370 + packages/geometry/lib/opentype.js | 14459 ++++++++++++++++ packages/geometry/package.json | 27 + packages/geometry/parser/FontParser.ts | 17 + packages/geometry/tsconfig.json | 30 + packages/geometry/vite.config.js | 26 + samples/geometry/Sample_ExtrudeGeometry.ts | 189 + samples/geometry/Sample_TextGeometry.ts | 64 + src/assets/Res.ts | 2 +- 19 files changed, 16075 insertions(+), 1 deletion(-) create mode 100644 packages/geometry/ExtrudeGeometry/Curve/CubicBezierCurve2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/Curve/Curve2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/Curve/LineCurve2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/Curve/QuadraticBezierCurve2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/ExtrudeGeometry.ts create mode 100644 packages/geometry/ExtrudeGeometry/Path2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/Shape2D.ts create mode 100644 packages/geometry/ExtrudeGeometry/ShapeUtils.ts create mode 100644 packages/geometry/TextGeometry.ts create mode 100644 packages/geometry/index.ts create mode 100644 packages/geometry/lib/opentype.d.ts create mode 100644 packages/geometry/lib/opentype.js create mode 100644 packages/geometry/package.json create mode 100644 packages/geometry/parser/FontParser.ts create mode 100644 packages/geometry/tsconfig.json create mode 100644 packages/geometry/vite.config.js create mode 100644 samples/geometry/Sample_ExtrudeGeometry.ts create mode 100644 samples/geometry/Sample_TextGeometry.ts diff --git a/packages/geometry/ExtrudeGeometry/Curve/CubicBezierCurve2D.ts b/packages/geometry/ExtrudeGeometry/Curve/CubicBezierCurve2D.ts new file mode 100644 index 00000000..741426b2 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Curve/CubicBezierCurve2D.ts @@ -0,0 +1,58 @@ +import { Vector2 } from "@orillusion/core"; +import { Curve2D } from "./Curve2D"; + +export class CubicBezierCurve2D extends Curve2D { + public v0: Vector2; + public v1: Vector2; + public v2: Vector2; + public v3: Vector2; + + constructor(v0: Vector2, v1: Vector2, v2: Vector2, v3: Vector2) { + super(); + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public get points(): Vector2[] { + return [this.v0, this.v1, this.v2, this.v3]; + } + + public getPoint(t: number, result: Vector2 = new Vector2()): Vector2 { + result.set( + this.cubicBezier(t, this.v0.x, this.v1.x, this.v2.x, this.v3.x), + this.cubicBezier(t, this.v0.y, this.v1.y, this.v2.y, this.v3.y) + ); + return result; + } + + public copyFrom(other: CubicBezierCurve2D) { + this.v0.copyFrom(other.v0); + this.v1.copyFrom(other.v1); + this.v2.copyFrom(other.v2); + this.v3.copyFrom(other.v3); + } + + protected cubicBezierP0(t: number, p: number) { + const k = 1 - t; + return k * k * k * p; + } + + protected cubicBezierP1(t: number, p: number) { + const k = 1 - t; + return 3 * k * k * t * p; + } + + protected cubicBezierP2(t: number, p: number) { + return 3 * (1 - t) * t * t * p; + } + + protected cubicBezierP3(t: number, p: number) { + return t * t * t * p; + } + + protected cubicBezier(t: number, p0: number, p1: number, p2: number, p3: number) { + return this.cubicBezierP0(t, p0) + this.cubicBezierP1(t, p1) + this.cubicBezierP2(t, p2) + this.cubicBezierP3(t, p3); + } +} diff --git a/packages/geometry/ExtrudeGeometry/Curve/Curve2D.ts b/packages/geometry/ExtrudeGeometry/Curve/Curve2D.ts new file mode 100644 index 00000000..297246a9 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Curve/Curve2D.ts @@ -0,0 +1,30 @@ +import { Vector2 } from "@orillusion/core"; + +export enum CurveType { + LineCurve, + SplineCurve, + EllipseCurve, + QuadraticBezierCurve, +} + +export class Curve2D { + public curveType: CurveType; + + public get points(): Vector2[] { + console.warn("points not implementation!"); + return []; + } + + public getPoint(t: number, result: Vector2 = new Vector2()): Vector2 { + console.warn("getPoint not implementation!"); + return result; + } + + public getPoints(divisions = 5): Vector2[] { + let points: Vector2[] = []; + for (let d = 0; d <= divisions; d++) { + points.push(this.getPoint(d / divisions)); + } + return points; + } +} diff --git a/packages/geometry/ExtrudeGeometry/Curve/LineCurve2D.ts b/packages/geometry/ExtrudeGeometry/Curve/LineCurve2D.ts new file mode 100644 index 00000000..24a73fb8 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Curve/LineCurve2D.ts @@ -0,0 +1,47 @@ +import { Vector2 } from "@orillusion/core"; +import { Curve2D, CurveType } from "./Curve2D"; + +export class LineCurve2D extends Curve2D { + public v0: Vector2; + public v1: Vector2; + + constructor(v0: Vector2, v1: Vector2) { + super(); + this.v0 = v0; + this.v1 = v1; + this.curveType = CurveType.LineCurve; + } + + public get points(): Vector2[] { + return [this.v0, this.v1]; + } + + public getPoint(t: number, result: Vector2 = new Vector2()): Vector2 { + if (t >= 1) { + result.copyFrom(this.v1); + } else { + this.v1.sub(this.v0, result); + result.multiplyScaler(t).add(this.v0, result); + } + return result; + } + + public getPointAt(u: number, result: Vector2 = new Vector2()): Vector2 { + return this.getPoint(u, result); + } + + public getTangent(t: number, result: Vector2 = new Vector2()): Vector2 { + this.v1.sub(this.v0, result); + result.normalize(); + return result; + } + + public getTangentAt(u: number, result: Vector2 = new Vector2()): Vector2 { + return this.getTangent(u, result); + } + + public copyFrom(other: LineCurve2D) { + this.v0.copyFrom(other.v0); + this.v1.copyFrom(other.v1); + } +} diff --git a/packages/geometry/ExtrudeGeometry/Curve/QuadraticBezierCurve2D.ts b/packages/geometry/ExtrudeGeometry/Curve/QuadraticBezierCurve2D.ts new file mode 100644 index 00000000..7165b362 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Curve/QuadraticBezierCurve2D.ts @@ -0,0 +1,51 @@ +import { Vector2 } from "@orillusion/core"; +import { Curve2D, CurveType } from "./Curve2D"; + +export class QuadraticBezierCurve2D extends Curve2D { + public v0: Vector2; + public v1: Vector2; + public v2: Vector2; + + constructor(v0: Vector2, v1: Vector2, v2: Vector2) { + super(); + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.curveType = CurveType.QuadraticBezierCurve; + } + + public get points(): Vector2[] { + return [this.v0, this.v1, this.v2]; + } + + public getPoint(t: number, result: Vector2 = new Vector2()): Vector2 { + result.set( + this.quadraticBezier(t, this.v0.x, this.v1.x, this.v2.x), + this.quadraticBezier(t, this.v0.y, this.v1.y, this.v2.y) + ); + return result; + } + + public copyFrom(other: QuadraticBezierCurve2D) { + this.v0.copyFrom(other.v0); + this.v1.copyFrom(other.v1); + this.v2.copyFrom(other.v2); + } + + protected quadraticBezierP0(t: number, p: number) { + const k = 1 - t; + return k * k * p; + } + + protected quadraticBezierP1(t: number, p: number) { + return 2 * (1 - t) * t * p; + } + + protected quadraticBezierP2(t: number, p: number) { + return t * t * p; + } + + protected quadraticBezier(t: number, p0: number, p1: number, p2: number): number { + return this.quadraticBezierP0(t, p0) + this.quadraticBezierP1(t, p1) + this.quadraticBezierP2(t, p2); + } +} diff --git a/packages/geometry/ExtrudeGeometry/ExtrudeGeometry.ts b/packages/geometry/ExtrudeGeometry/ExtrudeGeometry.ts new file mode 100644 index 00000000..308799fc --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/ExtrudeGeometry.ts @@ -0,0 +1,431 @@ +import { GeometryBase, Vector2, VertexAttributeName } from "@orillusion/core"; +import { Shape2D } from "./Shape2D"; +import { ShapeUtils } from "./ShapeUtils"; + +export type ExtrudeGeometryArgs = { + curveSegments?: number; + steps?: number; + depth?: number; + bevelEnabled?: boolean; + bevelThickness?: number; + bevelSize?: number; + bevelOffset?: number; + bevelSegments?: number; +} + +export class ExtrudeGeometry extends GeometryBase { + public shapes: Shape2D[]; + public options: ExtrudeGeometryArgs; + protected verticesArray: number[] = []; + protected uvArray: number[] = []; + + constructor(shapes?: Shape2D[], options?) { + super(); + this.options = options; + this.shapes = shapes; + if (this.shapes && this.shapes.length > 0) { + this.buildGeometry(options); + } + } + + protected buildGeometry(options: ExtrudeGeometryArgs) { + this.verticesArray = []; + this.uvArray = []; + + for (let shape of this.shapes) { + this.addShape(shape, options); + } + + const indices = new Uint32Array(this.verticesArray.length / 3); + for (let i = 0; i < indices.length; i++) { + indices[i] = i; + } + + this.setIndices(indices); + this.setAttribute(VertexAttributeName.position, new Float32Array(this.verticesArray)); + this.setAttribute(VertexAttributeName.normal, new Float32Array(this.verticesArray.length)); + this.setAttribute(VertexAttributeName.uv, new Float32Array(this.uvArray)); + + this.computeNormals(); + } + + protected addGroup(start: number, count: number, materialIndex = 0) { + this.addSubGeometry({ + indexStart: start, + indexCount: count, + vertexStart: 0, + vertexCount: 0, + firstStart: 0, + index: 0, + topology: 0, + }); + } + + protected addShape(shape: Shape2D, options: ExtrudeGeometryArgs) { + const verticesArray = this.verticesArray; + const uvArray = this.uvArray; + const self = this; + + const curveSegments: number = options.curveSegments !== undefined ? options.curveSegments : 12; + const steps: number = options.steps !== undefined ? options.steps : 1; + const depth: number = options.depth !== undefined ? options.depth : 1; + + let bevelEnabled: boolean = options.bevelEnabled !== undefined ? options.bevelEnabled : true; + let bevelThickness: number = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; + let bevelSize: number = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; + let bevelOffset: number = options.bevelOffset !== undefined ? options.bevelOffset : 0; + let bevelSegments: number = options.bevelSegments !== undefined ? options.bevelSegments : 3; + + if (!bevelEnabled) { + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + bevelOffset = 0; + } + + const placeholder: number[] = []; + const shapePoints = shape.extractPoints(curveSegments); + let vertices = shapePoints.shape; + const holes = shapePoints.holes; + const reverse = !ShapeUtils.isClockWise(vertices); + + if (reverse) { + vertices = vertices.reverse(); + for (let i = 0; i < holes.length; i++) { + const hole = holes[i]; + if (ShapeUtils.isClockWise(hole)) { + holes[i] = hole.reverse(); + } + } + } + + const faces = ShapeUtils.triangulateShape(vertices, holes); + + const contour = vertices; + for (let i = 0; i < holes.length; i++) { + const hole = holes[i]; + vertices = vertices.concat(hole); + } + + const vlen = vertices.length, flen = faces.length; + + const contourMovements = []; + for (let i = 0, j = contour.length - 1, k = i + 1; i < contour.length; i++, j++, k++) { + if (j === contour.length) j = 0; + if (k === contour.length) k = 0; + contourMovements[i] = this.getBevelVec(contour[i], contour[j], contour[k]); + } + + const holesMovements = []; + let oneHoleMovements, verticesMovements = contourMovements.concat(); + for (let h = 0, hl = holes.length; h < hl; h++) { + const ahole = holes[h]; + oneHoleMovements = []; + for (let i = 0, j = ahole.length - 1, k = i + 1; i < ahole.length; i++, j++, k++) { + if (j === ahole.length) j = 0; + if (k === ahole.length) k = 0; + oneHoleMovements[i] = this.getBevelVec(ahole[i], ahole[j], ahole[k]); + } + holesMovements.push(oneHoleMovements); + verticesMovements = verticesMovements.concat(oneHoleMovements); + } + + for (let b = 0; b < bevelSegments; b++) { + const t = b / bevelSegments; + const z = bevelThickness * Math.cos(t * Math.PI / 2); + const bs = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset; + + for (let i = 0; i < contour.length; i++) { + const vert = this.scalePoint2(contour[i], contourMovements[i], bs); + v(vert.x, vert.y, - z); + } + + for (let h = 0, hl = holes.length; h < hl; h++) { + const ahole = holes[h]; + oneHoleMovements = holesMovements[h]; + for (let i = 0; i < ahole.length; i++) { + const vert = this.scalePoint2(ahole[i], oneHoleMovements[i], bs); + v(vert.x, vert.y, - z); + } + } + } + + const bs = bevelSize + bevelOffset; + for (let i = 0; i < vlen; i++) { + const vert = bevelEnabled ? this.scalePoint2(vertices[i], verticesMovements[i], bs) : vertices[i]; + v(vert.x, vert.y, 0); + } + + for (let s = 1; s <= steps; s++) { + for (let i = 0; i < vlen; i++) { + const vert = bevelEnabled ? this.scalePoint2(vertices[i], verticesMovements[i], bs) : vertices[i]; + v(vert.x, vert.y, depth / steps * s); + } + } + + for (let b = bevelSegments - 1; b >= 0; b--) { + const t = b / bevelSegments; + const z = bevelThickness * Math.cos(t * Math.PI / 2); + const bs = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset; + + for (let i = 0, il = contour.length; i < il; i++) { + const vert = this.scalePoint2(contour[i], contourMovements[i], bs); + v(vert.x, vert.y, depth + z); + } + + for (let h = 0, hl = holes.length; h < hl; h++) { + const ahole = holes[h]; + oneHoleMovements = holesMovements[h]; + for (let i = 0, il = ahole.length; i < il; i++) { + const vert = this.scalePoint2(ahole[i], oneHoleMovements[i], bs); + v(vert.x, vert.y, depth + z); + } + } + } + + function buildLidFaces() { + const start = verticesArray.length / 3; + + if (bevelEnabled) { + let layer = 0; + let offset = vlen * layer; + + // Bottom faces + for (let i = 0; i < flen; i++) { + const face = faces[i]; + f3(face[2] + offset, face[1] + offset, face[0] + offset); + } + + layer = steps + bevelSegments * 2; + offset = vlen * layer; + + // Top faces + for (let i = 0; i < flen; i++) { + const face = faces[i]; + f3(face[0] + offset, face[1] + offset, face[2] + offset); + } + + } else { + // Bottom faces + for (let i = 0; i < flen; i++) { + const face = faces[i]; + f3(face[2], face[1], face[0]); + } + + // Top faces + for (let i = 0; i < flen; i++) { + const face = faces[i]; + f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps); + } + } + + self.addGroup(start, verticesArray.length / 3 - start, 0); + } + + function buildSideFaces() { + const start = verticesArray.length / 3; + let layeroffset = 0; + sidewalls(contour, layeroffset); + layeroffset += contour.length; + + for (let h = 0, hl = holes.length; h < hl; h++) { + const ahole = holes[h]; + sidewalls(ahole, layeroffset); + layeroffset += ahole.length; + } + + self.addGroup(start, verticesArray.length / 3 - start, 1); + } + + function sidewalls(contour: Vector2[], layeroffset: number) { + let i = contour.length; + while (--i >= 0) { + const j = i; + let k = i - 1; + if (k < 0) k = contour.length - 1; + for (let s = 0, sl = (steps + bevelSegments * 2); s < sl; s++) { + const slen1 = vlen * s; + const slen2 = vlen * (s + 1); + + const a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; + + f4(a, b, c, d); + } + } + } + + function v(x: number, y: number, z: number) { + placeholder.push(x); + placeholder.push(y); + placeholder.push(z); + } + + function f3(a: number, b: number, c: number) { + addVertex(a); + addVertex(b); + addVertex(c); + + const nextIndex = verticesArray.length / 3; + const uvs = WorldUVGenerator.generateTopUV(verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1); + + addUV(uvs[0]); + addUV(uvs[1]); + addUV(uvs[2]); + } + + function f4(a: number, b: number, c: number, d: number) { + addVertex(a); + addVertex(b); + addVertex(d); + + addVertex(b); + addVertex(c); + addVertex(d); + + const nextIndex = verticesArray.length / 3; + const uvs = WorldUVGenerator.generateSideWallUV(verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1); + + addUV(uvs[0]); + addUV(uvs[1]); + addUV(uvs[3]); + + addUV(uvs[1]); + addUV(uvs[2]); + addUV(uvs[3]); + } + + function addVertex(index: number) { + verticesArray.push(placeholder[index * 3 + 0]); + verticesArray.push(placeholder[index * 3 + 1]); + verticesArray.push(placeholder[index * 3 + 2]); + } + + function addUV(vector2: Vector2) { + uvArray.push(vector2.x); + uvArray.push(vector2.y); + } + + buildLidFaces(); + + buildSideFaces(); + } + + protected scalePoint2(pt: Vector2, vec: Vector2, size: number) { + return pt.clone().addScaledVector(vec, size); + } + + protected getBevelVec(inPt: Vector2, inPrev: Vector2, inNext: Vector2) { + let v_trans_x, v_trans_y, shrink_by; + + const v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; + const v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; + + const v_prev_lensq = (v_prev_x * v_prev_x + v_prev_y * v_prev_y); + + const collinear0 = (v_prev_x * v_next_y - v_prev_y * v_next_x); + + if (Math.abs(collinear0) > Number.EPSILON) { + const v_prev_len = Math.sqrt(v_prev_lensq); + const v_next_len = Math.sqrt(v_next_x * v_next_x + v_next_y * v_next_y); + + const ptPrevShift_x = (inPrev.x - v_prev_y / v_prev_len); + const ptPrevShift_y = (inPrev.y + v_prev_x / v_prev_len); + + const ptNextShift_x = (inNext.x - v_next_y / v_next_len); + const ptNextShift_y = (inNext.y + v_next_x / v_next_len); + + const sf = ((ptNextShift_x - ptPrevShift_x) * v_next_y - (ptNextShift_y - ptPrevShift_y) * v_next_x) / (v_prev_x * v_next_y - v_prev_y * v_next_x); + + v_trans_x = (ptPrevShift_x + v_prev_x * sf - inPt.x); + v_trans_y = (ptPrevShift_y + v_prev_y * sf - inPt.y); + + const v_trans_lensq = (v_trans_x * v_trans_x + v_trans_y * v_trans_y); + if (v_trans_lensq <= 2) { + return new Vector2(v_trans_x, v_trans_y); + } else { + shrink_by = Math.sqrt(v_trans_lensq / 2); + } + + } else { + let direction_eq = false; + if (v_prev_x > Number.EPSILON) { + if (v_next_x > Number.EPSILON) { + direction_eq = true; + } + } else { + if (v_prev_x < - Number.EPSILON) { + if (v_next_x < - Number.EPSILON) { + direction_eq = true; + } + } else { + if (Math.sign(v_prev_y) === Math.sign(v_next_y)) { + direction_eq = true; + } + } + } + + if (direction_eq) { + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt(v_prev_lensq); + } else { + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt(v_prev_lensq / 2); + } + } + + return new Vector2(v_trans_x / shrink_by, v_trans_y / shrink_by); + } +} + +class WorldUVGenerator { + public static generateTopUV(vertices: any, indexA: number, indexB: number, indexC: number): Vector2[] { + const a_x = vertices[indexA * 3]; + const a_y = vertices[indexA * 3 + 1]; + const b_x = vertices[indexB * 3]; + const b_y = vertices[indexB * 3 + 1]; + const c_x = vertices[indexC * 3]; + const c_y = vertices[indexC * 3 + 1]; + + return [ + new Vector2(a_x, a_y), + new Vector2(b_x, b_y), + new Vector2(c_x, c_y) + ]; + } + + public static generateSideWallUV(vertices: any, indexA: number, indexB: number, indexC: number, indexD: number): Vector2[] { + const a_x = vertices[indexA * 3]; + const a_y = vertices[indexA * 3 + 1]; + const a_z = vertices[indexA * 3 + 2]; + const b_x = vertices[indexB * 3]; + const b_y = vertices[indexB * 3 + 1]; + const b_z = vertices[indexB * 3 + 2]; + const c_x = vertices[indexC * 3]; + const c_y = vertices[indexC * 3 + 1]; + const c_z = vertices[indexC * 3 + 2]; + const d_x = vertices[indexD * 3]; + const d_y = vertices[indexD * 3 + 1]; + const d_z = vertices[indexD * 3 + 2]; + + if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) { + return [ + new Vector2(a_x, 1 - a_z), + new Vector2(b_x, 1 - b_z), + new Vector2(c_x, 1 - c_z), + new Vector2(d_x, 1 - d_z) + ]; + } + + return [ + new Vector2(a_y, 1 - a_z), + new Vector2(b_y, 1 - b_z), + new Vector2(c_y, 1 - c_z), + new Vector2(d_y, 1 - d_z) + ]; + } +} diff --git a/packages/geometry/ExtrudeGeometry/Path2D.ts b/packages/geometry/ExtrudeGeometry/Path2D.ts new file mode 100644 index 00000000..310d66d3 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Path2D.ts @@ -0,0 +1,107 @@ +import { Vector2 } from "@orillusion/core"; +import { CubicBezierCurve2D } from "./Curve/CubicBezierCurve2D"; +import { Curve2D, CurveType } from "./Curve/Curve2D"; +import { LineCurve2D } from "./Curve/LineCurve2D"; +import { QuadraticBezierCurve2D } from "./Curve/QuadraticBezierCurve2D"; + +export class Path2D { + + public autoClose: boolean = false; + + protected curves: Array = []; + protected currentPoint: Vector2 = new Vector2(); + + constructor(points?: Vector2[]) { + if (points) { + this.setFromPoints(points); + } + } + + public getPoints(divisions: number): Vector2[] { + let last; + const points: Vector2[] = []; + + for (let i = 0, curves = this.curves; i < curves.length; i++) { + + const curve = curves[i]; + const resolution = curve.curveType == CurveType.EllipseCurve ? divisions * 2 + : (curve.curveType == CurveType.LineCurve) ? 1 + : curve.curveType == CurveType.SplineCurve ? divisions * curve.points.length + : divisions; + + const pts = curve.getPoints(resolution); + + for (let j = 0; j < pts.length; j++) { + const point = pts[j]; + + if (last && last.equals(point)) + continue; + + points.push(point); + last = point; + } + } + + if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) { + points.push(points[0]); + } + + return points; + } + + public setFromPoints(points: Vector2[]) { + this.moveTo(points[0].x, points[0].y); + for (let i = 1; i < points.length; i++) { + this.lineTo(points[i].x, points[i].y); + } + return this; + } + + public moveTo(x: number, y: number) { + this.currentPoint.set(x, y); + return this; + } + + public lineTo(x: number, y: number) { + this.curves.push(new LineCurve2D(this.currentPoint.clone(), new Vector2(x, y))); + this.currentPoint.set(x, y); + return this; + } + + public quadraticCurveTo(cpX: number, cpY: number, x: number, y: number) { + this.curves.push(new QuadraticBezierCurve2D(this.currentPoint.clone(), new Vector2(cpX, cpY), new Vector2(x, y))); + this.currentPoint.set(x, y); + return this; + } + + public bezierCurveTo(cp1X: number, cp1Y: number, cp2X: number, cp2Y: number, x: number, y: number) { + this.curves.push(new CubicBezierCurve2D(this.currentPoint.clone(), new Vector2(cp1X, cp1Y), new Vector2(cp2X, cp2Y), new Vector2(x, y))); + this.currentPoint.set(x, y); + return this; + } + + public isIntersect(path: Path2D): boolean { + let pathA: Vector2[] = this.getPoints(1); + let pathB: Vector2[] = path.getPoints(1); + return this.pointInPolygon(pathB[0], pathA); + } + + public pointInPolygon(point: Vector2, polygon: Vector2[]): boolean { + let inside = false; + const x = point.x, y = point.y; + const vertices = polygon; + let j = vertices.length - 1; + + for (let i = 0; i < vertices.length; i++) { + const xi = vertices[i].x, yi = vertices[i].y; + const xj = vertices[j].x, yj = vertices[j].y; + + const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + j = i; + } + + return inside; + } + +} diff --git a/packages/geometry/ExtrudeGeometry/Shape2D.ts b/packages/geometry/ExtrudeGeometry/Shape2D.ts new file mode 100644 index 00000000..d5bf5b0d --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/Shape2D.ts @@ -0,0 +1,26 @@ +import { Vector2 } from "@orillusion/core"; +import { Path2D } from "./Path2D"; + + +export class Shape2D extends Path2D { + public holes: Path2D[] = []; + + constructor(points?: Vector2[]) { + super(points); + } + + public extractPoints(divisions: number): { shape: Vector2[], holes: Vector2[][] } { + return { + shape: this.getPoints(divisions), + holes: this.getPointsHoles(divisions) + }; + } + + public getPointsHoles(divisions: number): Vector2[][] { + const holesPts: Vector2[][] = []; + for (let i = 0, l = this.holes.length; i < l; i++) { + holesPts[i] = this.holes[i].getPoints(divisions); + } + return holesPts; + } +} diff --git a/packages/geometry/ExtrudeGeometry/ShapeUtils.ts b/packages/geometry/ExtrudeGeometry/ShapeUtils.ts new file mode 100644 index 00000000..a68e3528 --- /dev/null +++ b/packages/geometry/ExtrudeGeometry/ShapeUtils.ts @@ -0,0 +1,57 @@ +import { Vector2, Vector3 } from "@orillusion/core"; +import { Earcut } from "@orillusion/graphic"; + +export class ShapeUtils { + public static isClockWise(points: Vector2[]): boolean { + return ShapeUtils.area(points) < 0; + } + + public static area(contour: Vector2[]) { + let a: number = 0.0; + const n = contour.length; + for (let p = n - 1, q = 0; q < n; p = q++) { + a += contour[p].x * contour[q].y - contour[q].x * contour[p].y; + } + return a * 0.5; + } + + public static triangulateShape(contour: Vector2[], holes: Vector2[][]) { + const faces: number[][] = []; + const vertices: number[] = []; + const holeIndices: number[] = []; + + removeDupEndPoints(contour); + addContour(vertices, contour); + + let holeIndex = contour.length; + holes.forEach(removeDupEndPoints); + + for (let i = 0; i < holes.length; i++) { + holeIndices.push(holeIndex); + holeIndex += holes[i].length; + addContour(vertices, holes[i]); + } + + const triangles = Earcut.triangulate(vertices, holeIndices); + + for (let i = 0; i < triangles.length; i += 3) { + faces.push(triangles.slice(i, i + 3)); + } + + return faces; + } +} + +function removeDupEndPoints(points: Vector2[]) { + const l = points.length; + if (l > 2 && points[l - 1].equals(points[0])) { + points.pop(); + } +} + +function addContour(vertices: number[], contour: Vector2[]) { + for (let i = 0; i < contour.length; i++) { + vertices.push(contour[i].x); + vertices.push(contour[i].y); + } +} diff --git a/packages/geometry/TextGeometry.ts b/packages/geometry/TextGeometry.ts new file mode 100644 index 00000000..834c2339 --- /dev/null +++ b/packages/geometry/TextGeometry.ts @@ -0,0 +1,75 @@ +import { Shape2D } from "./ExtrudeGeometry/Shape2D"; +import { ExtrudeGeometry, ExtrudeGeometryArgs } from "./ExtrudeGeometry/ExtrudeGeometry"; +import { ShapeUtils } from "./ExtrudeGeometry/ShapeUtils"; +import { Font } from "./lib/opentype"; + +export type TextGeometryArgs = ExtrudeGeometryArgs & { + font: Font; + fontSize: number; +} + +export class TextGeometry extends ExtrudeGeometry { + private _text: string; + declare public options: TextGeometryArgs; + constructor(text: string, options: TextGeometryArgs) { + super([], options); + this.options = options; + this.text = text; + } + + public get font(): Font { + return this.options.font; + } + + public get text(): string { + return this._text + } + + public get fontSize(): number { + return this.options.fontSize; + } + + public set fontSize(v: number) { + this.options.fontSize = v; + } + + public set text(v: string) { + this._text = v; + let paths = this.font.getPath(v, 0, 0, this.fontSize); + this.buildShape(paths); + this.buildGeometry(this.options); + } + + protected buildShape(path: any) { + let first: any, latest: any; + let shape2D = new Shape2D(); + const commands = path.commands; + for (let i = 0; i < commands.length; i++) { + const c = commands[i]; + shape2D = shape2D || new Shape2D(); + switch (c.type) { + case 'M': shape2D.moveTo(c.x, -c.y); first = c; break; + case 'L': shape2D.lineTo(c.x, -c.y); latest = c; break; + case 'C': shape2D.bezierCurveTo(c.x1, -c.y1, c.x2, -c.y2, c.x, -c.y); latest = c; break; + case 'Q': shape2D.quadraticCurveTo(c.x1, -c.y1, c.x, -c.y); latest = c; break; + case 'Z': + shape2D.lineTo(first.x, -first.y); + if (ShapeUtils.isClockWise(shape2D.getPoints(1))) { + this.shapes.push(shape2D); + } else { + for (let shape of this.shapes) { + if (shape.isIntersect(shape2D)) { + shape.holes.push(shape2D); + } + } + } + shape2D = null; + break; + } + } + + if (shape2D) { + this.shapes.push(shape2D); + } + } +} diff --git a/packages/geometry/index.ts b/packages/geometry/index.ts new file mode 100644 index 00000000..eda175ee --- /dev/null +++ b/packages/geometry/index.ts @@ -0,0 +1,10 @@ +export * from "./ExtrudeGeometry/Curve/Curve2D" +export * from "./ExtrudeGeometry/Curve/LineCurve2D" +export * from "./ExtrudeGeometry/Curve/CubicBezierCurve2D" +export * from "./ExtrudeGeometry/Curve/QuadraticBezierCurve2D" +export * from "./ExtrudeGeometry/ExtrudeGeometry" +export * from "./ExtrudeGeometry/Path2D" +export * from "./ExtrudeGeometry/Shape2D" +export * from "./ExtrudeGeometry/ShapeUtils" +export * from "./parser/FontParser" +export * from "./TextGeometry" \ No newline at end of file diff --git a/packages/geometry/lib/opentype.d.ts b/packages/geometry/lib/opentype.d.ts new file mode 100644 index 00000000..5b77c6e4 --- /dev/null +++ b/packages/geometry/lib/opentype.d.ts @@ -0,0 +1,370 @@ +export as namespace opentype; + +/****************************************** + * FONT + ******************************************/ + +export class Font { + names: FontNames; + unitsPerEm: number; + ascender: number; + descender: number; + createdTimestamp: number; + tables: { [tableName: string]: Table }; + supported: boolean; + glyphs: GlyphSet; + encoding: Encoding; + substitution: Substitution; + + readonly defaultRenderOptions: RenderOptions; + + constructor(options: FontConstructorOptions); + + charToGlyph(c: string): Glyph; + charToGlyphIndex(s: string): number; + download(fileName?: string): void; + draw( + ctx: CanvasRenderingContext2D, + text: string, + x?: number, + y?: number, + fontSize?: number, + options?: RenderOptions, + ): void; + drawMetrics( + ctx: CanvasRenderingContext2D, + text: string, + x?: number, + y?: number, + fontSize?: number, + options?: RenderOptions, + ): void; + drawPoints( + ctx: CanvasRenderingContext2D, + text: string, + x?: number, + y?: number, + fontSize?: number, + options?: RenderOptions, + ): void; + forEachGlyph( + text: string, + x: number | undefined, + y: number | undefined, + fontSize: number | undefined, + options: RenderOptions | undefined, + callback: (glyph: Glyph, x: number, y: number, fontSize: number, options?: RenderOptions) => void, + ): number; + getAdvanceWidth(text: string, fontSize?: number, options?: RenderOptions): number; + getEnglishName(name: string): string; + getKerningValue(leftGlyph: Glyph | number, rightGlyph: Glyph | number): number; + getPath(text: string, x: number, y: number, fontSize: number, options?: RenderOptions): Path; + getPaths(text: string, x: number, y: number, fontSize: number, options?: RenderOptions): Path[]; + glyphIndexToName(gid: number): string; + glyphNames: GlyphNames; + hasChar(c: string): boolean; + kerningPairs: KerningPairs; + nameToGlyph(name: string): Glyph; + nameToGlyphIndex(name: string): number; + numberOfHMetrics: number; + numGlyphs: number; + outlinesFormat: string; + stringToGlyphs(s: string): Glyph[]; + toArrayBuffer(): ArrayBuffer; + toBuffer(): ArrayBuffer; + toTables(): Table; + validate(): void; +} + +export type FontConstructorOptions = + & FontConstructorOptionsBase + & Partial + & { + glyphs: Glyph[]; + }; + +export interface FontOptions { + empty?: boolean | undefined; + familyName: string; + styleName: string; + fullName?: string | undefined; + postScriptName?: string | undefined; + designer?: string | undefined; + designerURL?: string | undefined; + manufacturer?: string | undefined; + manufacturerURL?: string | undefined; + license?: string | undefined; + licenseURL?: string | undefined; + version?: string | undefined; + description?: string | undefined; + copyright?: string | undefined; + trademark?: string | undefined; + unitsPerEm: number; + ascender: number; + descender: number; + createdTimestamp: number; + weightClass?: string | undefined; + widthClass?: string | undefined; + fsSelection?: string | undefined; +} + +export interface FontConstructorOptionsBase { + familyName: string; + styleName: string; + unitsPerEm: number; + ascender: number; + descender: number; +} + +export interface FontNames { + copyright: LocalizedName; + description: LocalizedName; + designer: LocalizedName; + designerURL: LocalizedName; + fontFamily: LocalizedName; + fontSubfamily: LocalizedName; + fullName: LocalizedName; + license: LocalizedName; + licenseURL: LocalizedName; + manufacturer: LocalizedName; + manufacturerURL: LocalizedName; + postScriptName: LocalizedName; + trademark: LocalizedName; + version: LocalizedName; +} + +export interface Table { + [propName: string]: any; + encode(): number[]; + fields: Field[]; + sizeOf(): number; + tables: Table[]; + tableName: string; +} + +export interface KerningPairs { + [pair: string]: number; +} + +export interface LocalizedName { + [lang: string]: string; +} + +export interface Field { + name: string; + type: string; + value: any; +} + +/****************************************** + * GLYPH + ******************************************/ + +export class Glyph { + index: number; + name: string | null; + unicode?: number | undefined; + unicodes: number[]; + xMin?: number | undefined; + xMax?: number | undefined; + yMin?: number | undefined; + yMax?: number | undefined; + advanceWidth?: number | undefined; + leftSideBearing?: number | undefined; + path: Path; + + private bindConstructorValues(options: GlyphOptions): void; + constructor(options: GlyphOptions); + + addUnicode(unicode: number): void; + getBoundingBox(): BoundingBox; + getPath(x?: number, y?: number, fontSize?: number, options?: RenderOptions, font?: Font): Path; + getContours(): Contour; + getMetrics(): Metrics; + draw(ctx: CanvasRenderingContext2D, x?: number, y?: number, fontSize?: number, options?: RenderOptions): void; + drawPoints(ctx: CanvasRenderingContext2D, x?: number, y?: number, fontSize?: number, options?: RenderOptions): void; + drawMetrics( + ctx: CanvasRenderingContext2D, + x?: number, + y?: number, + fontSize?: number, + options?: RenderOptions, + ): void; +} + +export interface GlyphOptions { + index?: number | undefined; + name?: string | undefined; + unicode?: number | undefined; + unicodes?: number[] | undefined; + xMin?: number | undefined; + yMin?: number | undefined; + xMax?: number | undefined; + yMax?: number | undefined; + advanceWidth?: number | undefined; + leftSideBearing?: number | undefined; + path?: Path | (() => Path) | undefined; +} + +export class GlyphNames { + private names; + constructor(post: Post); + glyphIndexToName(gid: number): string; + nameToGlyphIndex(name: string): number; +} + +export class GlyphSet { + private font; + private glyphs; + constructor(font: Font, glyphs: Glyph[] | Array<() => Glyph>); + get(index: number): Glyph; + length: number; + push(index: number, loader: () => Glyph): void; +} + +export interface Post { + glyphNameIndex?: number[] | undefined; + isFixedPitch: number; + italicAngle: number; + maxMemType1: number; + minMemType1: number; + maxMemType42: number; + minMemType42: number; + names?: string[] | undefined; + numberOfGlyphs?: number | undefined; + offset?: number[] | undefined; + underlinePosition: number; + underlineThickness: number; + version: number; +} + +export interface RenderOptions { + script?: string | undefined; + language?: string | undefined; + kerning?: boolean | undefined; + xScale?: number | undefined; + yScale?: number | undefined; + letterSpacing?: number | undefined; + tracking?: number | undefined; + features?: + | { + [key: string]: boolean; + } + | undefined; +} + +export interface Metrics { + leftSideBearing: number; + rightSideBearing?: number | undefined; + xMax: number; + xMin: number; + yMax: number; + yMin: number; +} + +export interface Contour extends Array {} + +export interface Point { + lastPointOfContour?: boolean | undefined; +} + +/****************************************** + * PATH + ******************************************/ + +export class Path { + fill: string | null; + stroke: string | null; + strokeWidth: number; + constructor(); + bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number): void; + close: () => void; + closePath(): void; + commands: PathCommand[]; + curveTo: (x1: number, y1: number, x2: number, y2: number, x: number, y: number) => void; + draw(ctx: CanvasRenderingContext2D): void; + extend(pathOrCommands: Path | PathCommand[] | BoundingBox): void; + getBoundingBox(): BoundingBox; + lineTo(x: number, y: number): void; + moveTo(x: number, y: number): void; + quadraticCurveTo(x1: number, y1: number, x: number, y: number): void; + quadTo: (x1: number, y1: number, x: number, y: number) => void; + toDOMElement(decimalPlaces: number): SVGPathElement; + toPathData(decimalPlaces: number): string; + toSVG(decimalPlaces: number): string; + unitsPerEm: number; +} + +export type PathCommand = + | { + type: "M"; + x: number; + y: number; + } + | { + type: "L"; + x: number; + y: number; + } + | { + type: "C"; + x1: number; + y1: number; + x2: number; + y2: number; + x: number; + y: number; + } + | { + type: "Q"; + x1: number; + y1: number; + x: number; + y: number; + } + | { + type: "Z"; + }; + +/****************************************** + * UTIL CLASSES + ******************************************/ + +export class BoundingBox { + x1: number; + y1: number; + x2: number; + y2: number; + + isEmpty(): boolean; + addPoint(x: number, y: number): void; + addX(x: number): void; + addY(y: number): void; + addBezier(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x: number, y: number): void; + addQuad(x0: number, y0: number, x1: number, y1: number, x: number, y: number): void; +} + +export interface Encoding { + charset: string; + charToGlyphIndex(c: string): number; + font: Font; +} + +export type Substitution = (font: Font) => any; +// TODO add methods + +/****************************************** + * STATIC + ******************************************/ + +export function load(url: string, callback: (error: any, font?: Font) => void): void; +export function load(url: string): Promise; + +export function loadSync( + url: string, + opt?: { + lowMemory: boolean; + }, +): Font; + +export function parse(buffer: any): Font; \ No newline at end of file diff --git a/packages/geometry/lib/opentype.js b/packages/geometry/lib/opentype.js new file mode 100644 index 00000000..e25606aa --- /dev/null +++ b/packages/geometry/lib/opentype.js @@ -0,0 +1,14459 @@ +/** + * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens + */ + +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +var tinyInflate = tinf_uncompress; + +// The Bounding Box object + +function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; +} +/** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ +function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; +} + +/** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ +BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); +}; + +/** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } +}; + +/** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ +BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); +}; + +/** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); +}; + +/** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } + } +}; + +/** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); +}; + +// Geometric objects + +/** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; + +/** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; + +/** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; + +/** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ + +/** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; + +/** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; + +/** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ +Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; +}; + +/** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } +}; + +/** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var arguments$1 = arguments; + + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; +}; + +/** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; +/** + * @constant + * @type {number} + */ +sizeOf.BYTE = constant(1); + +/** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.CHAR = constant(1); + +/** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ +encode.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + v = ''; + console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); + } + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); + } + + return b; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + return 0; + } + return v.length; +}; + +/** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.USHORT = constant(2); + +/** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.SHORT = constant(2); + +/** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.UINT24 = constant(3); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.ULONG = constant(4); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONG = constant(4); + +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; + +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; + +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; + +/** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ +encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONGDATETIME = constant(8); + +/** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.TAG = constant(4); + +// CFF data types /////////////////////////////////////////////////////////// + +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; + +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; + +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; + +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; + +// Convert a numeric operand or charstring number to a variable-size list of bytes. +/** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; + +/** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER16 = constant(3); + +/** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER32 = constant(5); + +/** + * @param {number} + * @returns {Array} + */ +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } + + return out; +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; + +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; + +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } + + return b; +}; + +/** + * @param {string} + * @returns {number} + */ +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; + +// Data for converting old eight-bit Macintosh encodings to Unicode. +// This representation is optimized for decoding; encoding is slower +// and needs more memory. The assumption is that all opentype.js users +// want to open fonts, but saving a font will be comparatively rare +// so it can be more expensive. Keyed by IANA character set name. +// +// Python script for generating these strings: +// +// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) +// print(s.encode('utf-8')) +/** + * @private + */ +var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' +}; + +/** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ +decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } + + return result; +}; + +// Helper function for encode.MACSTRING. Returns a dictionary for mapping +// Unicode character codes to their 8-bit MacOS equivalent. This table +// is not exactly a super cheap data structure, but we do not care because +// encoding Macintosh strings is only rarely needed in typical applications. +var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); +var macEncodingCacheKeys; +var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } + + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } + + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } + + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } + + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } + + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } + + return encodingTable; +}; + +/** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ +encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } + + return result; +}; + +/** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ +sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } +}; + +// Helper for encode.VARDELTAS +function isByteEncodable(value) { + return value >= -128 && value <= 127; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } + + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } + + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } + + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } + + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; +} + +/** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ +encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; +}; + +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +/** + * @param {Array} l + * @returns {Array} + */ +encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; + +/** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; + +/** + * @param {number} + * @returns {Array} + */ +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; + +/** + * @param {Array} v + * @param {string} + * @returns {Array} + */ +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; +}; + +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; + +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); + +/** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ +encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; + +// Utility functions //////////////////////////////////////////////////////// + +/** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); +}; + +/** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } + } + + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } + + return d; +}; + +/** + * @param {opentype.Table} + * @returns {number} + */ +sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + numBytes += sizeOfFunction(value); + + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; + } + } + + return numBytes; +}; + +encode.RECORD = encode.TABLE; +sizeOf.RECORD = sizeOf.TABLE; + +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; + +sizeOf.LITERAL = function(v) { + return v.length; +}; + +// Table metadata + +/** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ +function Table(tableName, fields, options) { + // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, + // as this will result in wrong encoding order of the coverage data on serialization to bytes. + // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. + if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } +} + +/** + * Encodes the table and returns an array of bytes + * @return {Array} + */ +Table.prototype.encode = function() { + return encode.TABLE(this); +}; + +/** + * Get the size of the table. + * @return {number} + */ +Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); +}; + +/** + * @private + */ +function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; +} + +/** + * @private + */ +function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; +} + +/** + * @private + */ +function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; +} + +// Common Layout Tables + +/** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else if (coverageTable.format === 2) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 2}] + .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { + return [ + {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; + })) + ); + } else { + check.assert(false, 'Coverage format must be 1 or 2.'); + } +} +Coverage.prototype = Object.create(Table.prototype); +Coverage.prototype.constructor = Coverage; + +function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); +} +ScriptList.prototype = Object.create(Table.prototype); +ScriptList.prototype.constructor = ScriptList; + +/** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); +} +FeatureList.prototype = Object.create(Table.prototype); +FeatureList.prototype.constructor = FeatureList; + +/** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ +function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); +} +LookupList.prototype = Object.create(Table.prototype); +LookupList.prototype.constructor = LookupList; + +// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) +// Don't use offsets inside Records (probable bug), only in Tables. +var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, +}; + +// Parsing utility functions + +// Retrieve an unsigned byte from the DataView. +function getByte(dataView, offset) { + return dataView.getUint8(offset); +} + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); +} + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +function getShort(dataView, offset) { + return dataView.getInt16(offset, false); +} + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +function getULong(dataView, offset) { + return dataView.getUint32(offset, false); +} + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +} + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +} + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +} + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +} + +// Convert the list of bytes to a string. +function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +} + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; + +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseCard8 = Parser.prototype.parseByte; + +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + +Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +// + Since until 2038 those bits will be filled by zeros we can ignore them. +Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; +}; + +///// Parsing lists and records /////////////////////////////// + +// Parse a list of 32 bit unsigned integers. +Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; + } + + this.relativeOffset += count * 4; + return offsets; +}; + +// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream +// or provided as an argument. +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; + +// Parses a list of 16 bit signed integers. +Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return list; +}; + +// Parses a list of bytes. +Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } + + this.relativeOffset += count; + return list; +}; + +/** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ +Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +/** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ +Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +// Parse a data structure into an object +// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } +Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } +}; + +/** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ +Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; +}; + +/** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ +Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; +}; + +Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +/** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ +Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; +}; + +///// Complex tables parsing ////////////////////////////////// + +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +// parser.offset must point to the start of the table containing the coverage. +Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); +}; + +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); +}; + +///// Static methods /////////////////////////////////// +// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. + +Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; +}; + +Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; +}; + +Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; +}; + +Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; +}; + +Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; +}; + +Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; +}; + +Parser.tag = Parser.prototype.parseTag; +Parser.byte = Parser.prototype.parseByte; +Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; +Parser.uShortList = Parser.prototype.parseUShortList; +Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; +Parser.uLongList = Parser.prototype.parseULongList; +Parser.struct = Parser.prototype.parseStruct; +Parser.coverage = Parser.prototype.parseCoverage; +Parser.classDef = Parser.prototype.parseClassDef; + +///// Script, Feature, Lookup lists /////////////////////////////////////////////// +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm + +var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList +}; + +Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; +}; + +Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; +}; + +Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; +}; + +Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; +}; + +var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, +}; + +// The `cmap` table stores the mappings from characters to glyphs. + +function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } +} + +function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } +} + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4 and 12. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } + + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +// Make cmap table, format 4 by default, 12 if needed only +function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } + + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + var t = new table.Table('cmap', cmapTable); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } + + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; +} + +var cmap = { parse: parseCmapTable, make: makeCmapTable }; + +// Glyph encoding + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +/** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } + return null; +}; + +/** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ +function CmapEncoding(cmap) { + this.cmap = cmap; +} + +/** + * @param {string} c - the character + * @return {number} The glyph index. + */ +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; +}; + +/** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +/** + * @param {string} s - The character + * @return {number} The index. + */ +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +/** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ +function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; + } +} + +/** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +/** + * @param {number} gid + * @return {string} + */ +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); + } + } +} + +function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } +} + +/** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ +function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } +} + +// Drawing utility functions. + +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +var draw = { line: line }; + +// The Glyph object +// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + +function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; +} +/** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +/** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +/** + * @param {GlyphOptions} + */ +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } + + if ('yMin' in options) { + this.yMin = options.yMin; + } + + if ('xMax' in options) { + this.xMax = options.xMax; + } + + if ('yMax' in options) { + this.yMax = options.yMax; + } + + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; + +/** + * @param {number} + */ +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); +}; + +/** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ +Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); +}; + +/** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ +Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; +}; + +/** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +/** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; + +/** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ +Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; + +/** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; + +// The GlyphSet object + +// Define a property on the glyph that depends on the path being loaded. +function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); +} + +/** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; + } + } + + this.length = (glyphs && glyphs.length) || 0; +} + +/** + * @param {number} index + * @return {opentype.Glyph} + */ +GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } + + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + } + + return this.glyphs[index]; +}; + +/** + * @param {number} index + * @param {Object} + */ +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; + +/** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ +function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); +} + +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; + }; +} +/** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + +// The `CFF` table contains the glyph outlines in PostScript format. + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; +} + +function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + return {offsets: offsets, startOffset: start, endOffset: endOffset}; +} +function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } + + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; +} + +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); +} + +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); +} + +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; +} + +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); +} + +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; +} + +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Returns a list of "Top DICT"s found using an INDEX list. +// Used to read both the usual high-level Top DICTs and also the FDArray +// discovered inside CID-keyed fonts. When a Top DICT has a reference to +// a Private DICT that is read and saved into the Top DICT. +// +// In addition to the expected/optional values as outlined in TOP_DICT_META +// the following values might be saved into the Top DICT. +// +// _subrs [] array of local CFF subroutines from Private DICT +// _subrsBias bias value computed from number of subroutines +// (see calcCFFSubroutineBias() and parseCFFCharstring()) +// _defaultWidthX default widths for CFF characters +// _nominalWidthX bias added to width embedded within glyph description +// +// _privateDict saved copy of parsed Private DICT from Top DICT +function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; +} + +function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; +} + +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } + + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } + + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } +} + +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; +} + +function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} + +function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; +} + +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; +} + +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} + +function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} + +function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; +} + +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} + +function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: Math.round(_13 * x + _23 * cmd.x1), + y1: Math.round(_13 * y + _23 * cmd.y1), + x2: Math.round(_13 * cmd.x + _23 * cmd.x1), + y2: Math.round(_13 * cmd.y + _23 * cmd.y1) + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; +} + +function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +var cff = { parse: parseCFFTable, make: makeCFFTable }; + +// The `head` table contains global information about the font. + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +var head = { parse: parseHeadTable, make: makeHeadTable }; + +// The `hhea` table contains information for horizontal layout. + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +var hhea = { parse: parseHheaTable, make: makeHheaTable }; + +// The `hmtx` table contains the horizontal metrics for all glyphs. + +function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; + } +} + +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } +} + +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; +} + +var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + +// The `ltag` table stores IETF BCP-47 language tags. It allows supporting + +function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); + + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; + } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; +} + +function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); + + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; +} + +var ltag = { make: makeLtagTable, parse: parseLtagTable }; + +// The `maxp` table establishes the memory requirements for the font. + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + +// The `name` naming table. + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' +}; + +// MacOS language ID → MacOS script ID +// +// Note that the script ID is not sufficient to determine what encoding +// to use in TrueType files. For some languages, MacOS used a modification +// of a mainstream script. For example, an Icelandic name would be stored +// with smRoman in the TrueType naming table, but the actual encoding +// is a special Icelandic version of the normal Macintosh Roman encoding. +// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal +// Syllables but MacOS had run out of available script codes, so this was +// done as a (pretty radical) "modification" of Ethiopic. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman +}; + +// While Microsoft indicates a region/country for all its language +// IDs, we omit the region code if it's equal to the "most likely +// region subtag" according to Unicode CLDR. For scripts, we omit +// the subtag if it is equal to the Suppress-Script entry in the +// IANA language subtag registry for IETF BCP 47. +// +// For example, Microsoft states that its language code 0x041A is +// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' +// and not 'hr-HR' because Croatia is the default country for Croatian, +// according to Unicode CLDR. As another example, Microsoft states +// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform +// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script +// for the Croatian language, according to IANA. +// +// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html +// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry +var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' +}; + +// Returns a IETF BCP 47 language code, for example 'zh-Hant' +// for 'Chinese in the traditional script'. +function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } + + break; + + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; +} + +var utf16 = 'utf-16'; + +// MacOS script ID → encoding. This table stores the default case, +// which can be overridden by macLanguageEncodings. +var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic +}; + +// MacOS language ID → encoding. This table stores the exceptional +// cases, which override macScriptEncodings. For writing MacOS naming +// tables, we need to emit a MacOS script ID. Therefore, we cannot +// merge macScriptEncodings into macLanguageEncodings. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript +}; + +function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; + + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; + } + + break; + } + + return undefined; +} + +// Parse the naming `name` table. +// FIXME: Format 1 additional fields are not supported yet. +// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. +function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + } + + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } + + translations[language] = text; + } + } + } + + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; +} + +// {23: 'foo'} → {'foo': 23} +// ['bar', 'baz'] → {'bar': 0, 'baz': 1} +function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +// Finds the position of needle in haystack, or -1 if not there. +// Like String.indexOf(), but for arrays. +function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } + + return pos; + } + } + + return -1; +} + +function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } + + } + + return offset; +} + +function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; + + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } + + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); + } + + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } + } + } + + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); + + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; +} + +var _name = { parse: parseNameTable, make: makeNameTable }; + +// The `OS/2` table contains metrics required in OpenType fonts. + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + +// The `post` table stores additional PostScript information, such as glyph names. + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; + } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} + +var post = { parse: parsePostTable, make: makePostTable }; + +// The `GSUB` table contains ligatures, among other things. + +var subtableParsers = new Array(9); // subtableParsers[0] is unused + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS +subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS +subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS +subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS +subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; +}; + +var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF +subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC +subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES +subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS +subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/gsub.htm +function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GSUB Writing ////////////////////////////////////////////// +var subtableMakers = new Array(9); + +subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } +}; + +subtableMakers[2] = function makeLookup2(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { + return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); + }))); +}; + +subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); +}; + +subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); +}; + +subtableMakers[6] = function makeLookup6(subtable) { + if (subtable.substFormat === 1) { + var returnTable = new table.Table('chainContextTable', [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { + return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { + var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) + .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) + .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) + .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); + + chainRule.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + return new table.Table('chainRuleTable', tableData); + })); + }))); + return returnTable; + } else if (subtable.substFormat === 2) { + check.assert(false, 'lookup type 6 format 2 is not yet supported.'); + } else if (subtable.substFormat === 3) { + var tableData = [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; + + tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); + subtable.backtrackCoverage.forEach(function (coverage, i) { + tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); + subtable.inputCoverage.forEach(function (coverage, i) { + tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); + subtable.lookaheadCoverage.forEach(function (coverage, i) { + tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + + tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); + subtable.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + + var returnTable$1 = new table.Table('chainContextTable', tableData); + + return returnTable$1; + } + + check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); +}; + +function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); +} + +var gsub = { parse: parseGsubTable, make: makeGsubTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +// Parse the metadata `meta` table. +// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html +function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; +} + +function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); + + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; + + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + + return result; +} + +var meta = { parse: parseMetaTable, make: makeMetaTable }; + +// The `sfnt` wrapper provides organization for the tables in the font. + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} + +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + } + + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); + + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (metaTable) { + tables.push(metaTable); + } + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + +// The Layout object is the prototype of Substitution objects, and provides + +function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +// binary search in a list of ranges (coverage, class definition) +function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } +} + +/** + * @exports opentype.Layout + * @class + */ +function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; +} + +Layout.prototype = { + + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, + + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, + + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, + + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, + + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + +}; + +// The Position object provides utility methods to manipulate + +/** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Position(font) { + Layout.call(this, font, 'gpos'); +} + +Position.prototype = Layout.prototype; + +/** + * Init some data for faster and easier access later. + */ +Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); +}; + +/** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ +Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; +}; + +/** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ +Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } +}; + +// The Substitution object provides utility methods to manipulate + +/** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Substitution(font) { + Layout.call(this, font, 'gsub'); +} + +// Check if 2 arrays of primitives are equal. +function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; +} + +// Find the first subtable of a lookup table in a particular format. +function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; +} + +Substitution.prototype = Layout.prototype; + +/** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ +Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; +}; + +/** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; +}; + +/** + * List all multiple substitutions (lookup type 2) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('ccmp', 'stch') + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } + } + } + return substitutions; +}; + +/** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ +Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; +}; + +/** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ +Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; +}; + +/** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; +}; + +/** + * Add or modify a multiple substitution (lookup type 2) + * @param {string} feature - 4-letter feature name ('ccmp', 'stch') + * @param {Object} substitution - { sub: id, by: [id] } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); + var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + sequences: [] + }); + check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; +}; + +/** + * Add or modify an alternate substitution (lookup type 3) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; +}; + +/** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } +}; + +/** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': + return this.getLigatures(feature, script, language); + case 'ccmp': + return this.getMultiple(feature, script, language) + .concat(this.getLigatures(feature, script, language)); + case 'stch': + return this.getMultiple(feature, script, language); + } + return undefined; +}; + +/** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + case 'ccmp': + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return undefined; +}; + +function isBrowser() { + return typeof window !== 'undefined'; +} + +function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + + return ab; +} + +function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + + return buffer; +} + +function checkArgument(expression, message) { + if (!expression) { + throw message; + } +} + +// The `glyf` table describes the glyphs in TrueType outline format. + +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } +} + +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} + +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } + + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; + + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + + p.closePath(); + } + return p; +} + +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); +} + +function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; +} + +function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } +} + +var glyf = { getPath: getPath, parse: parseGlyfTable}; + +/* A TrueType font hinting interpreter. +* +* (c) 2017 Axel Kittenberger +* +* This interpreter has been implemented according to this documentation: +* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html +* +* According to the documentation F24DOT6 values are used for pixels. +* That means calculation is 1/64 pixel accurate and uses integer operations. +* However, Javascript has floating point operations by default and only +* those are available. One could make a case to simulate the 1/64 accuracy +* exactly by truncating after every division operation +* (for example with << 0) to get pixel exactly results as other TrueType +* implementations. It may make sense since some fonts are pixel optimized +* by hand using DELTAP instructions. The current implementation doesn't +* and rather uses full floating point precision. +* +* xScale, yScale and rotation is currently ignored. +* +* A few non-trivial instructions are missing as I didn't encounter yet +* a font that used them to test a possible implementation. +* +* Some fonts seem to use undocumented features regarding the twilight zone. +* Only some of them are implemented as they were encountered. +* +* The exports.DEBUG statements are removed on the minified distribution file. +*/ + +var instructionTable; +var exec; +var execGlyph; +var execComponent; + +/* +* Creates a hinting object. +* +* There ought to be exactly one +* for each truetype font that is used for hinting. +*/ +function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; +} + +/* +* Not rounding. +*/ +function roundOff(v) { + return v; +} + +/* +* Rounding to grid. +*/ +function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); +} + +/* +* Rounding to double grid. +*/ +function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; +} + +/* +* Rounding to half grid. +*/ +function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); +} + +/* +* Rounding to up to grid. +*/ +function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); +} + +/* +* Rounding to down to grid. +*/ +function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); +} + +/* +* Super rounding. +*/ +var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; +}; + +/* +* Unit vector of x-axis. +*/ +var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + + // Slope of vector line. + slope: 0, + + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; + } +}; + +/* +* Unit vector of y-axis. +*/ +var yUnitVector = { + x: 0, + + y: 1, + + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } +}; + +Object.freeze(xUnitVector); +Object.freeze(yUnitVector); + +/* +* Creates a unit vector that is not x- or y-axis. +*/ +function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); +} + +/* +* Gets the projected distance between two points. +* o1/o2 ... if true, respective original position is used. +*/ +UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); +}; + +/* +* Moves point p so the moved position has the same relative +* position to the moved positions of rp1 and rp2 than the +* original positions had. +* +* See APPENDIX on INTERPOLATE at the bottom of this file. +*/ +UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); +}; + +/* +* Sets the point 'p' relative to point 'rp' +* by the distance 'd' +* +* See APPENDIX on SETRELATIVE at the bottom of this file. +* +* p ... point to set +* rp ... reference point +* d ... distance on projection vector +* pv ... projection vector (undefined = this) +* org ... if true, uses the original position of rp as reference. +*/ +UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; +}; + +/* +* Touches the point p. +*/ +UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; +}; + +/* +* Returns a unit vector with x/y coordinates. +*/ +function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } +} + +/* +* Creates a point in the hinting engine. +*/ +function HPoint( + x, + y, + lastPointOfContour, + onCurve +) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); +} + +/* +* Returns the next touched point on the contour. +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; +}; + +/* +* Returns the previous touched point on the contour +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + + return p; +}; + +/* +* The zero point. +*/ +var HPZero = Object.freeze(new HPoint(0, 0)); + +/* +* The default state of the interpreter. +* +* Note: Freezing the defaultState and then deriving from it +* makes the V8 Javascript engine going awkward, +* so this is avoided, albeit the defaultState shouldn't +* ever change. +*/ +var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true +}; + +/* +* The current state of the interpreter. +* +* env ... 'fpgm' or 'prep' or 'glyf' +* prog ... the program +*/ +function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } +} + +/* +* Executes a glyph program. +* +* This does the hinting for each glyph. +* +* Returns an array of moved points. +* +* glyph: the glyph to hint +* ppem: the size the glyph is rendered for +*/ +Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; + + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } + + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } + + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } + + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } + + if (this._errorState > 1) { return; } + + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } +}; + +/* +* Executes the hinting program for a glyph. +*/ +execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } + + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); + + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + + state.contours = contours; + + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } + + exec(state); + + gZone.length -= 2; + } + } + + return gZone; +}; + +/* +* Executes the hinting program for a component of a multi-component glyph +* or of the glyph itself for a non-component glyph. +*/ +execComponent = function(glyph, state, xScale, yScale) +{ + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); + } + + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + + if (state.inhibitGridFit) { return; } + + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + exec(state); + + // Removes the extra points. + gZone.length -= 2; + + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } +}; + +/* +* Executes the program loaded in state. +*/ +exec = function(state) { + var prog = state.prog; + + if (!prog) { return; } + + var pLen = prog.length; + var ins; + + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; + + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } + + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } + + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } + + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } +}; + +/* +* Initializes the twilight zone. +* +* This is only done if a SZPx instruction +* refers to the twilight zone. +*/ +function initTZone(state) +{ + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } +} + +/* +* Skips the instruction pointer ahead over an IF/ELSE block. +* handleElse .. if true breaks on matching ELSE +*/ +function skip(state, handleElse) +{ + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; +} + +/*----------------------------------------------------------* +* And then a lot of instructions... * +*----------------------------------------------------------*/ + +// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis +// 0x00-0x01 +function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; +} + +// SPVTCA[a] Set Projection Vector to Coordinate Axis +// 0x02-0x03 +function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; +} + +// SFVTCA[a] Set Freedom Vector to Coordinate Axis +// 0x04-0x05 +function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; +} + +// SPVTL[a] Set Projection Vector To Line +// 0x06-0x07 +function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); +} + +// SFVTL[a] Set Freedom Vector To Line +// 0x08-0x09 +function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); +} + +// SPVFS[] Set Projection Vector From Stack +// 0x0A +function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); +} + +// SFVFS[] Set Freedom Vector From Stack +// 0x0B +function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); +} + +// GPV[] Get Projection Vector +// 0x0C +function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); +} + +// GFV[] Get Freedom Vector +// 0x0C +function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); +} + +// SFVTPV[] Set Freedom Vector To Projection Vector +// 0x0E +function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } +} + +// ISECT[] moves point p to the InterSECTion of two lines +// 0x0F +function ISECT(state) +{ + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; +} + +// SRP0[] Set Reference Point 0 +// 0x10 +function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } +} + +// SRP1[] Set Reference Point 1 +// 0x11 +function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } +} + +// SRP1[] Set Reference Point 2 +// 0x12 +function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } +} + +// SZP0[] Set Zone Pointer 0 +// 0x13 +function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP1[] Set Zone Pointer 1 +// 0x14 +function SZP1(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP2[] Set Zone Pointer 2 +// 0x15 +function SZP2(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZPS[] Set Zone PointerS +// 0x16 +function SZPS(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SLOOP[] Set LOOP variable +// 0x17 +function SLOOP(state) { + state.loop = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } +} + +// RTG[] Round To Grid +// 0x18 +function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + + state.round = roundToGrid; +} + +// RTHG[] Round To Half Grid +// 0x19 +function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + + state.round = roundToHalfGrid; +} + +// SMD[] Set Minimum Distance +// 0x1A +function SMD(state) { + var d = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + + state.minDis = d / 0x40; +} + +// ELSE[] ELSE clause +// 0x1B +function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + + skip(state, false); +} + +// JMPR[] JuMP Relative +// 0x1C +function JMPR(state) { + var o = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } + + // A jump by 1 would do nothing. + state.ip += o - 1; +} + +// SCVTCI[] Set Control Value Table Cut-In +// 0x1D +function SCVTCI(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + + state.cvCutIn = n / 0x40; +} + +// DUP[] DUPlicate top stack element +// 0x20 +function DUP(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + + stack.push(stack[stack.length - 1]); +} + +// POP[] POP top stack element +// 0x21 +function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + + state.stack.pop(); +} + +// CLEAR[] CLEAR the stack +// 0x22 +function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + + state.stack.length = 0; +} + +// SWAP[] SWAP the top two elements on the stack +// 0x23 +function SWAP(state) { + var stack = state.stack; + + var a = stack.pop(); + var b = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + + stack.push(a); + stack.push(b); +} + +// DEPTH[] DEPTH of the stack +// 0x24 +function DEPTH(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + + stack.push(stack.length); +} + +// LOOPCALL[] LOOPCALL function +// 0x2A +function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + + // restores the callers program + state.ip = cip; + state.prog = cprog; +} + +// CALL[] CALL function +// 0x2B +function CALL(state) { + var fn = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + exec(state); + + // restores the callers program + state.ip = cip; + state.prog = cprog; + + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } +} + +// CINDEX[] Copy the INDEXed element to the top of the stack +// 0x25 +function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); +} + +// MINDEX[] Move the INDEXed element to the top of the stack +// 0x26 +function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + + stack.push(stack.splice(stack.length - k, 1)[0]); +} + +// FDEF[] Function DEFinition +// 0x2C +function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + var fn = stack.pop(); + var ipBegin = ip; + + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + + while (prog[++ip] !== 0x2D){ } + + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); +} + +// MDAP[a] Move Direct Absolute Point +// 0x2E-0x2F +function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// IUP[a] Interpolate Untouched Points through the outline +// 0x30 +function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + + v.interpolate(cp, pp, np, v); + } +} + +// SHP[] SHift Point using reference point +// 0x32-0x33 +function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + + state.loop = 1; +} + +// SHC[] SHift Contour using reference point +// 0x36-0x37 +function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; + + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + + var d = pv.distance(rp, rp, false, true); + + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); +} + +// SHZ[] SHift Zone using reference point +// 0x36-0x37 +function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } +} + +// SHPIX[] SHift point by a PIXel amount +// 0x38 +function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } + + fv.setRelative(p, p, d); + fv.touch(p); + } + + state.loop = 1; +} + +// IP[] Interpolate Point +// 0x39 +function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } + + fv.interpolate(p, rp1, rp2, pv); + + fv.touch(p); + } + + state.loop = 1; +} + +// MSIRP[a] Move Stack Indirect Relative Point +// 0x3A-0x3B +function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } +} + +// ALIGNRP[] Align to reference point. +// 0x3C +function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); + } + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; +} + +// RTG[] Round To Double Grid +// 0x3D +function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; +} + +// MIAP[a] Move Indirect Absolute Point +// 0x3E-0x3F +function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } + + var d = pv.distance(p, HPZero); + + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + + d = state.round(d); + } + + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// NPUSB[] PUSH N Bytes +// 0x40 +function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// NPUSHW[] PUSH N Words +// 0x41 +function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// WS[] Write Store +// 0x42 +function WS(state) { + var stack = state.stack; + var store = state.store; + + if (!store) { store = state.store = []; } + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; +} + +// RS[] Read Store +// 0x43 +function RS(state) { + var stack = state.stack; + var store = state.store; + + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RS', l); } + + var v = (store && store[l]) || 0; + + stack.push(v); +} + +// WCVTP[] Write Control Value Table in Pixel units +// 0x44 +function WCVTP(state) { + var stack = state.stack; + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; +} + +// RCVT[] Read Control Value Table entry +// 0x45 +function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + + stack.push(state.cvt[cvte] * 0x40); +} + +// GC[] Get Coordinate projected onto the projection vector +// 0x46-0x47 +function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); +} + +// MD[a] Measure Distance +// 0x49-0x4A +function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); + + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + + state.stack.push(Math.round(d * 64)); +} + +// MPPEM[] Measure Pixels Per EM +// 0x4B +function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); +} + +// FLIPON[] set the auto FLIP Boolean to ON +// 0x4D +function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; +} + +// LT[] Less Than +// 0x50 +function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + + stack.push(e1 < e2 ? 1 : 0); +} + +// LTEQ[] Less Than or EQual +// 0x53 +function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + + stack.push(e1 <= e2 ? 1 : 0); +} + +// GTEQ[] Greater Than +// 0x52 +function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); +} + +// GTEQ[] Greater Than or EQual +// 0x53 +function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + + stack.push(e1 >= e2 ? 1 : 0); +} + +// EQ[] EQual +// 0x54 +function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); +} + +// NEQ[] Not EQual +// 0x55 +function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); +} + +// ODD[] ODD +// 0x56 +function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); +} + +// EVEN[] EVEN +// 0x57 +function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); +} + +// IF[] IF test +// 0x58 +function IF(state) { + var test = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } +} + +// EIF[] End IF +// 0x59 +function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } +} + +// AND[] logical AND +// 0x5A +function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + + stack.push(e2 && e1 ? 1 : 0); +} + +// OR[] logical OR +// 0x5B +function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + + stack.push(e2 || e1 ? 1 : 0); +} + +// NOT[] logical NOT +// 0x5C +function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); +} + +// DELTAP1[] DELTA exception P1 +// DELTAP2[] DELTA exception P2 +// DELTAP3[] DELTA exception P3 +// 0x5D, 0x71, 0x72 +function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; + + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } +} + +// SDB[] Set Delta Base in the graphics state +// 0x5E +function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; +} + +// SDS[] Set Delta Shift in the graphics state +// 0x5F +function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + + state.deltaShift = Math.pow(0.5, n); +} + +// ADD[] ADD +// 0x60 +function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + + stack.push(n1 + n2); +} + +// SUB[] SUB +// 0x61 +function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + + stack.push(n1 - n2); +} + +// DIV[] DIV +// 0x62 +function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + + stack.push(n1 * 64 / n2); +} + +// MUL[] MUL +// 0x63 +function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + + stack.push(n1 * n2 / 64); +} + +// ABS[] ABSolute value +// 0x64 +function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + + stack.push(Math.abs(n)); +} + +// NEG[] NEGate +// 0x65 +function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + + stack.push(-n); +} + +// FLOOR[] FLOOR +// 0x66 +function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); +} + +// CEILING[] CEILING +// 0x67 +function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + + stack.push(Math.ceil(n / 0x40) * 0x40); +} + +// ROUND[ab] ROUND value +// 0x68-0x6B +function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); +} + +// WCVTF[] Write Control Value Table in Funits +// 0x70 +function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; +} + +// DELTAC1[] DELTA exception C1 +// DELTAC2[] DELTA exception C2 +// DELTAC3[] DELTA exception C3 +// 0x73, 0x74, 0x75 +function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } +} + +// SROUND[] Super ROUND +// 0x76 +function SROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// S45ROUND[] Super ROUND 45 degrees +// 0x77 +function S45ROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// ROFF[] Round Off +// 0x7A +function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + + state.round = roundOff; +} + +// RUTG[] Round Up To Grid +// 0x7C +function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + + state.round = roundUpToGrid; +} + +// RDTG[] Round Down To Grid +// 0x7D +function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + + state.round = roundDownToGrid; +} + +// SCANCTRL[] SCAN conversion ConTRoL +// 0x85 +function SCANCTRL(state) { + var n = state.stack.pop(); + + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } +} + +// SDPVTL[a] Set Dual Projection Vector To Line +// 0x86-0x87 +function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); +} + +// GETINFO[] GET INFOrmation +// 0x88 +function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } + + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. + + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } + + stack.push(r); +} + +// ROLL[] ROLL the top three stack elements +// 0x8A +function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + + stack.push(b); + stack.push(a); + stack.push(c); +} + +// MAX[] MAXimum of top two stack elements +// 0x8B +function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); +} + +// MIN[] MINimum of top two stack elements +// 0x8C +function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + + stack.push(Math.min(e1, e2)); +} + +// SCANTYPE[] SCANTYPE +// 0x8D +function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } +} + +// INSTCTRL[] INSTCTRL +// 0x8D +function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } +} + +// PUSHB[abc] PUSH Bytes +// 0xB0-0xB7 +function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// PUSHW[abc] PUSH Words +// 0xB8-0xBF +function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// MDRP[abcde] Move Direct Relative Point +// 0xD0-0xEF +// (if indirect is 0) +// +// and +// +// MIRP[abcde] Move Indirect Relative Point +// 0xE0-0xFF +// (if indirect is 1) + +function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } +} + +/* +* The instruction table. +*/ +instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) +]; + +/***************************** + Mathematical Considerations +****************************** + +fv ... refers to freedom vector +pv ... refers to projection vector +rp ... refers to reference point +p ... refers to to point being operated on +d ... refers to distance + +SETRELATIVE: +============ + +case freedom vector == x-axis: +------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + +case freedom vector == y-axis: +------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + +generic case: +------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + +INTERPOLATE: +============ + +Examples of point interpolation. + +The weight of the movement of the reference point gets bigger +the further the other reference point is away, thus the safest +option (that is avoiding 0/0 divisions) is to weight the +original distance of the other point by the sum of both distances. + +If the sum of both distances is 0, then move the point by the +arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + +------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + +vim: set ts=4 sw=4 expandtab: +*****/ + +/** + * Converts a string into a list of tokens. + */ + +/** + * Create a new token + * @param {string} char a single char + */ +function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; +} + +/** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ +function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; +} + +/** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ +function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; +} + +/** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + +/** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ +function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); +} + +/** + * Create an event instance + * @param {string} eventId event unique id + */ +function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; +} + +/** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ +function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); +} + +/** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ +function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); +} + +/** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ +Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; +}; + +Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; +}; + +/** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ +Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; +}; + +/** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ +Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); +}; + +/** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } +}; + +/** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } +}; + +/** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; +}; + +/** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } +}; + +/** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } +}; + +/** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ +Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); +}; + +/** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ +Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } +}; + +/** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ +Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); +}; + +/** + * Sets context params current value index + * @param {number} index context params current value index + */ +ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); +}; + +/** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ +ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } +}; + +/** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ +Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } +}; + +/** + * Converts all tokens into a string + */ +Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); +}; + +/** + * Get a context by name + * @param {string} contextName context name to get + */ +Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; +}; + +/** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ +Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } +}; + +/** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ +Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } +}; + +/** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ +Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; +}; + +/** + * Gets a context range tokens + * @param {contextRange} range a context range + */ +Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); +}; + +/** + * Gets the ranges of a context + * @param {string} contextName context name + */ +Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } +}; + +/** + * Resets context ranges to run context update + */ +Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } +}; + +/** + * Updates context ranges + */ +Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); +}; + +/** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ +Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; +}; + +/** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ +Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); +}; + +/** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ +Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; +}; + +// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ +// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ +// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ +// jscs:disable maximumLineLength +/** + * Check if a char is Arabic + * @param {string} c a single char + */ +function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); +} + +/** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ +function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); +} + +/** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ +function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); +} + +/** + * Check if a char is Latin + * @param {string} c a single char + */ +function isLatinChar(c) { + return /[A-z]/.test(c); +} + +/** + * Check if a char is whitespace char + * @param {string} c a single char + */ +function isWhiteSpace(c) { + return /\s/.test(c); +} + +/** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + +/** + * Create feature query instance + * @param {Font} font opentype font instance + */ +function FeatureQuery(font) { + this.font = font; + this.features = {}; +} + +/** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + +/** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ +function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; +} + +/** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ +function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; +} + +/** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; +} + +/** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; +} + +/** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ +function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; +} + +/** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ +function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; +} + +/** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; +} + +/** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ +function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; +} + +/** + * Get default script features indexes + */ +FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; +}; + +/** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); +}; + +/** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; +}; + +/** + * Get features of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; +}; + +/** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; +}; + +/** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } +}; + +/** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + +/** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + +/** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ +FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; +}; + +/** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ +FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; +}; + +/** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ +FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; +}; + +/** + * Get lookup table by index + * @param {number} index lookup table index + */ +FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; +}; + +/** + * Get lookup tables for a feature + * @param {string} feature + */ +FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); +}; + +/** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ +FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; +}; + +/** + * Arabic word context checkers + */ + +function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); +} + +function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); +} + +var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck +}; + +/** + * Arabic sentence context checkers + */ + +function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); +} + +function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } +} + +var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck +}; + +/** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); +} + +/** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } +} + +/** + * Supported substitutions + */ +var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 +}; + +/** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } +} + +/** + * Apply Arabic presentation forms to a range of tokens + */ + +/** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; +} + +/** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; +} + +/** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ +function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); +} + +/** + * Apply Arabic required ligatures feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); +} + +/** + * Latin word context checkers + */ + +function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); +} + +function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); +} + +var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck +}; + +/** + * Apply Latin ligature feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); +} + +/** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + +/** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ +function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; +} + +/** + * Sets Bidi text + * @param {string} text a text input + */ +Bidi.prototype.setText = function (text) { + this.text = text; +}; + +/** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ +Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck +}); + +/** + * Register arabic word check + */ +function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); +} + +/** + * Perform pre tokenization procedure then + * tokenize text input + */ +function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); +} + +/** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ +function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); +} + +/** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ +Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } +}; + +/** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ +Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } +}; + +/** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ +Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); +}; + +/** + * Check if 'glyphIndex' is registered + */ +function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } +} + +/** + * Apply arabic presentation forms features + */ +function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); +} + +/** + * Check if a context is registered + * @param {string} contextId context id + */ +Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); +}; + +/** + * Apply features to registered contexts + */ +Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } +}; + +/** + * process text input + * @param {string} text an input text + */ +Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } +}; + +/** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ +Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); +}; + +/** + * Get the current state index of each token + * @param {text} text an input text + */ +Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; +}; + +// The Font object + +/** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + +/** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ +function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); +} + +/** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; + +/** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; + +/** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * Update features + * @param {any} options features options + */ +Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); +}; + +/** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ +Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; +}; + +/** + * @param {string} + * @return {Number} + */ +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; + +/** + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * @param {Number} + * @return {String} + */ +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); +}; + +/** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; +}; + +/** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; + +/** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; +}; + +/** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; +}; + +/** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ +Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; +}; + +/** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ +Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); +}; + +/** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; + +/** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; + +/** + * @param {string} + * @return {string} + */ +Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } +}; + +/** + * Validate + */ +Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); +}; + +/** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; +/** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ +Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); +}; +/** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ +Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; +}; + +/** + * Initiate a download of the OpenType font. + */ +Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } +}; +/** + * @private + */ +Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 +}; + +/** + * @private + */ +Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 +}; + +/** + * @private + */ +Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 +}; + +// The `fvar` table stores font variation axes and instances. + +function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } + + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } + + if (nameID <= n) { + nameID = n + 1; + } + } + + names[nameID] = name; + return nameID; +} + +function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; +} + +function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; +} + +function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; + + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } + + return fields; +} + +function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined + + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } + + return inst; +} + +function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); + + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } + + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } + + return result; +} + +function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } + + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } + + return {axes: axes, instances: instances}; +} + +var fvar = { make: makeFvarTable, parse: parseFvarTable }; + +// The `GDEF` table contains various glyph properties + +var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; +}; + +var caretValue = function() { + var format = this.parseUShort(); + check.argument(format === 1 || format === 2 || format === 3, + 'Unsupported CaretValue table version.'); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + // Device / Variation Index tables unsupported + return { coordinate: this.parseShort() }; + } +}; + +var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); +}; + +var ligCaretList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + }; +}; + +var markGlyphSets = function() { + this.parseUShort(); // Version + return this.parseList(Parser.pointer(Parser.coverage)); +}; + +function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + 'Unsupported GDEF table version.'); + var gdef = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) + }; + if (tableVersion >= 1.2) { + gdef.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef; +} +var gdef = { parse: parseGDEFTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable +// this = Parser instance +subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); +}; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable +subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } +}; + +subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; +subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; +subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; +subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; +subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; +subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; +subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos +function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GPOS Writing ////////////////////////////////////////////// +// NOT SUPPORTED +var subtableMakers$1 = new Array(10); + +function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); +} + +var gpos = { parse: parseGposTable, make: makeGposTable }; + +// The `kern` table contains kerning pairs. + +function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; +} + +function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; +} + +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } +} + +var kern = { parse: parseKernTable }; + +// The `loca` table stores the offsets to the locations of the glyphs in the font. + +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; +} + +var loca = { parse: parseLocaTable }; + +// opentype.js + +/** + * The opentype library. + * @namespace opentype + */ + +// File loaders ///////////////////////////////////////////////////////// +/** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, nodeBufferToArrayBuffer(buffer)); + }); +} +/** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; + + request.onerror = function () { + callback('Font could not be loaded'); + }; + + request.send(); +} + +// Table Directory Entries ////////////////////////////////////////////// +/** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } + + return tableEntries; +} + +/** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } + + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; + } + + return tableEntries; +} + +/** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ + +/** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ +function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; + } +} + +// Public API /////////////////////////////////////////////////////////// + +/** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ +function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GDEF': + gdefTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } + } + + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; + + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } + + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); + + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } + + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } + + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + + return font; +} + +/** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ +function load(url, callback, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + var isNode = typeof window === 'undefined'; + var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } + }); + }); +} + +/** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ +function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); +} + +var opentype = /*#__PURE__*/Object.freeze({ + __proto__: null, + Font: Font, + Glyph: Glyph, + Path: Path, + BoundingBox: BoundingBox, + _parse: parse, + parse: parseBuffer, + load: load, + loadSync: loadSync +}); + +export default opentype; +export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; \ No newline at end of file diff --git a/packages/geometry/package.json b/packages/geometry/package.json new file mode 100644 index 00000000..747bdc16 --- /dev/null +++ b/packages/geometry/package.json @@ -0,0 +1,27 @@ +{ + "name": "@orillusion/geometry", + "version": "0.2.0", + "author": "Orillusion", + "description": "Orillusion geometry Plugin", + "main": "./dist/geometry.umd.js", + "module": "./dist/geometry.es.js", + "module:dev": "./index.ts", + "types": "./dist/index.d.ts", + "files": ["dist"], + "scripts": { + "dev": "vite", + "build": "vite build && npm run build:types && npm run build:clean", + "build:types": "tsc --emitDeclarationOnly -p tsconfig.json && mkdir dist/lib && cp lib/*.d.ts dist/lib/", + "build:clean": "mv dist/packages/geometry/* dist && rm -rf dist/src && rm -rf dist/packages", + "docs": "npm run docs:typedoc ../../docs/geometry index.ts", + "docs:typedoc": "npx typedoc --plugin typedoc-plugin-markdown --tsconfig tsconfig.json --gitRevision main --hideBreadcrumbs true --allReflectionsHaveOwnDocument true --readme none --excludeInternal --excludePrivate --excludeProtected --sort source-order --out" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/Orillusion/orillusion.git" + }, + "dependencies": { + "@orillusion/core": "^0.8.0" + } +} diff --git a/packages/geometry/parser/FontParser.ts b/packages/geometry/parser/FontParser.ts new file mode 100644 index 00000000..e875c2c2 --- /dev/null +++ b/packages/geometry/parser/FontParser.ts @@ -0,0 +1,17 @@ +import { ParserBase } from "@orillusion/core"; +import { parse, Font } from "../lib/opentype"; + +export class FontParser extends ParserBase { + declare public data: Font; + public async parseBuffer(buffer: ArrayBuffer) { + const font = parse(buffer); + this.data = font; + } + + public verification(): boolean { + if (this.data) { + return true; + } + throw new Error('Method not implemented.'); + } +} diff --git a/packages/geometry/tsconfig.json b/packages/geometry/tsconfig.json new file mode 100644 index 00000000..99cc83e4 --- /dev/null +++ b/packages/geometry/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "sourceMap": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "types": ["vite/client", "@webgpu/types"], + // for build + // "noEmit": true, + "declaration": true, + "declarationDir": "dist", + "outDir": "dist", + "skipLibCheck": true, + "noImplicitAny": false, + "noUnusedLocals": false, + "noUnusedParameters":false, + "noImplicitReturns": false, + "strictPropertyInitialization": false, + "strictNullChecks": false, + "strict": false, + "paths": { + "@orillusion/core": ["../../src"], + "@orillusion/*": ["../*"] + } + } +} \ No newline at end of file diff --git a/packages/geometry/vite.config.js b/packages/geometry/vite.config.js new file mode 100644 index 00000000..8905798e --- /dev/null +++ b/packages/geometry/vite.config.js @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite' +const path = require('path') +export default defineConfig({ + resolve: { + alias: { + '@orillusion/core': path.resolve(__dirname, '../../src'), + '@orillusion': path.resolve(__dirname, '../') + } + }, + build: { + target: 'esnext', + lib: { + entry: path.resolve('index.ts'), + name: 'Geometry', + fileName: (format) => `geometry.${format}.js` + }, + rollupOptions: { + external: ['@orillusion/core'], + output: { + globals: { + '@orillusion/core': 'Orillusion' + } + } + } + } +}) diff --git a/samples/geometry/Sample_ExtrudeGeometry.ts b/samples/geometry/Sample_ExtrudeGeometry.ts new file mode 100644 index 00000000..19bd7f90 --- /dev/null +++ b/samples/geometry/Sample_ExtrudeGeometry.ts @@ -0,0 +1,189 @@ +import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, Color, GridObject, Vector2, Vector3 } from "@orillusion/core"; +import { Shape2D, ExtrudeGeometry, Path2D } from "@orillusion/geometry"; + +class Sample_ExtrudeGeometry { + scene: Scene3D + async run() { + await Engine3D.init(); + let view = new View3D(); + view.scene = this.scene = new Scene3D(); + let sky = view.scene.addComponent(AtmosphericComponent); + + view.camera = CameraUtil.createCamera3DObject(view.scene); + view.camera.perspective(60, webGPUContext.aspect, 1, 5000.0); + view.camera.object3D.z = -15; + view.camera.object3D.addComponent(HoverCameraController).setCamera(0, -20, 500); + + let lightObj3D = new Object3D(); + let sunLight = lightObj3D.addComponent(DirectLight); + sunLight.intensity = 3; + sunLight.lightColor = KelvinUtil.color_temperature_to_rgb(6553); + sunLight.castShadow = true; + lightObj3D.rotationX = 53.2; + lightObj3D.rotationY = 220; + lightObj3D.rotationZ = 5.58; + view.scene.addChild(lightObj3D); + sky.relativeTransform = lightObj3D.transform; + + view.scene.addChild(new GridObject(1000, 100)) + + Engine3D.startRenderView(view); + + this.createShapes(); + } + + async createShapes() { + + // california + { + let points:Vector2[] = [] + points.push( new Vector2( 610, 320 ) ); + points.push( new Vector2( 450, 300 ) ); + points.push( new Vector2( 392, 392 ) ); + points.push( new Vector2( 266, 438 ) ); + points.push( new Vector2( 190, 570 ) ); + points.push( new Vector2( 190, 600 ) ); + points.push( new Vector2( 160, 620 ) ); + points.push( new Vector2( 160, 650 ) ); + points.push( new Vector2( 180, 640 ) ); + points.push( new Vector2( 165, 680 ) ); + points.push( new Vector2( 150, 670 ) ); + points.push( new Vector2( 90, 737 ) ); + points.push( new Vector2( 80, 795 ) ); + points.push( new Vector2( 50, 835 ) ); + points.push( new Vector2( 64, 870 ) ); + points.push( new Vector2( 60, 945 ) ); + points.push( new Vector2( 300, 945 ) ); + points.push( new Vector2( 300, 743 ) ); + points.push( new Vector2( 600, 473 ) ); + points.push( new Vector2( 626, 425 ) ); + points.push( new Vector2( 600, 370 ) ); + points.push( new Vector2( 610, 320 ) ); + + let shape = new Shape2D(points.map(p=>p.multiplyScaler(0.25))) + this.addShape(shape, -300, -60, 0) + } + + // triangle + { + let shape = new Shape2D(); + shape.moveTo(80, 20); + shape.lineTo(40, 80); + shape.lineTo(120, 80); + shape.lineTo(80, 20); + + this.addShape(shape, -180, 0, 0) + } + + // heart + { + const x = 0, y = 0; + const shape = new Shape2D() + .moveTo( x + 25, y + 25 ) + .bezierCurveTo( x + 25, y + 25, x + 20, y, x, y ) + .bezierCurveTo( x - 30, y, x - 30, y + 35, x - 30, y + 35 ) + .bezierCurveTo( x - 30, y + 55, x - 10, y + 77, x + 25, y + 95 ) + .bezierCurveTo( x + 60, y + 77, x + 80, y + 55, x + 80, y + 35 ) + .bezierCurveTo( x + 80, y + 35, x + 80, y, x + 50, y ) + .bezierCurveTo( x + 35, y, x + 25, y + 25, x + 25, y + 25 ); + + + this.addShape(shape, 0, 20, 0) + } + + // square + { + const sqLength = 80; + const squareShape = new Shape2D() + .moveTo( 0, 0 ) + .lineTo( 0, sqLength ) + .lineTo( sqLength, sqLength ) + .lineTo( sqLength, 0 ) + .lineTo( 0, 0 ); + + this.addShape(squareShape, 100, 20, 0) + } + + // Circle + { + const circleRadius = 40; + const circleShape = new Shape2D() + .moveTo( 0, circleRadius ) + .quadraticCurveTo( circleRadius, circleRadius, circleRadius, 0 ) + .quadraticCurveTo( circleRadius, - circleRadius, 0, - circleRadius ) + .quadraticCurveTo( - circleRadius, - circleRadius, - circleRadius, 0 ) + .quadraticCurveTo( - circleRadius, circleRadius, 0, circleRadius ); + + this.addShape(circleShape, 140, 150, 0) + } + + // Fish + { + const x = 0, y =0; + const fishShape = new Shape2D() + .moveTo( x, y ) + .quadraticCurveTo( x + 50, y - 80, x + 90, y - 10 ) + .quadraticCurveTo( x + 100, y - 10, x + 115, y - 40 ) + .quadraticCurveTo( x + 115, y, x + 115, y + 40 ) + .quadraticCurveTo( x + 100, y + 10, x + 90, y + 10 ) + .quadraticCurveTo( x + 50, y + 80, x, y ); + + this.addShape(fishShape, -40, 160, 0) + } + + // holes + { + const sqLength = 80; + const squareShape = new Shape2D() + .moveTo( 0, 0 ) + .lineTo( 0, sqLength ) + .lineTo( sqLength, sqLength ) + .lineTo( sqLength, 0 ) + .lineTo( 0, 0 ); + + let hole1 = new Path2D() + .moveTo( 10, 10 ) + .lineTo( 10, 30 ) + .lineTo( 30, 30 ) + .lineTo( 30, 10 ) + .lineTo( 10, 10 ); + + let hole2 = new Path2D() + .moveTo( 40, 10 ) + .lineTo( 40, 30 ) + .lineTo( 60, 30 ) + .lineTo( 60, 10 ) + .lineTo( 40, 10 ); + + squareShape.holes.push(hole1, hole2); + + this.addShape(squareShape, -150, 100, 0) + } + + } + + matrial: LitMaterial; + addShape(shape: Shape2D, x:number = 0, y:number = 0, z:number = 0){ + let obj = new Object3D(); + obj.localPosition = new Vector3(x, y, z) + let mr = obj.addComponent(MeshRenderer); + mr.geometry = new ExtrudeGeometry([shape], { + depth: 10, + bevelEnabled: false, + steps: 1 + }); + if(!this.matrial){ + let mat = this.matrial = new LitMaterial(); + mat.baseColor = new Color(0.2, 0.5, 1.0); + mat.castShadow = false; + } + let mats = []; + for (let i = 0; i < mr.geometry.subGeometries.length; i++) { + mats.push(this.matrial); + } + mr.materials = mats; + this.scene.addChild(obj); + } +} + +new Sample_ExtrudeGeometry().run(); diff --git a/samples/geometry/Sample_TextGeometry.ts b/samples/geometry/Sample_TextGeometry.ts new file mode 100644 index 00000000..ac7014c4 --- /dev/null +++ b/samples/geometry/Sample_TextGeometry.ts @@ -0,0 +1,64 @@ +import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer } from "@orillusion/core"; +import { TextGeometry, FontParser } from "@orillusion/geometry"; +import { Graphic3D } from "@orillusion/graphic"; + +class Sample_TextGeometry { + lightObj: Object3D; + async run() { + await Engine3D.init(); + let view = new View3D(); + view.scene = new Scene3D(); + let sky = view.scene.addComponent(AtmosphericComponent); + + view.camera = CameraUtil.createCamera3DObject(view.scene); + view.camera.perspective(60, webGPUContext.aspect, 1, 5000.0); + view.camera.object3D.z = -15; + view.camera.object3D.addComponent(HoverCameraController).setCamera(35, -20, 150); + + Engine3D.startRenderView(view); + + await this.createScene(view.scene); + sky.relativeTransform = this.lightObj.transform; + } + + async createScene(scene: Scene3D) { + { + scene.addChild(new Graphic3D()); + + let font = await Engine3D.res.load("/fonts/Roboto.ttf", FontParser); + + let obj = new Object3D(); + let mr = obj.addComponent(MeshRenderer); + mr.geometry = new TextGeometry("Hello, Orillusion!", { + font: font, // required + fontSize: 16, // required + depth: 2.5, + steps: 1, + bevelEnabled: false + }); + + let mats = []; + let mat = new LitMaterial(); + for (let i = 0; i < mr.geometry.subGeometries.length; i++) { + mats.push(mat); + } + mr.materials = mats; + + obj.x = mr.geometry.bounds.size.x * -0.5; + + scene.addChild(obj); + } + + let lightObj3D = this.lightObj = new Object3D(); + let sunLight = lightObj3D.addComponent(DirectLight); + sunLight.intensity = 3; + sunLight.lightColor = KelvinUtil.color_temperature_to_rgb(6553); + sunLight.castShadow = true; + lightObj3D.rotationX = 53.2; + lightObj3D.rotationY = 220; + lightObj3D.rotationZ = 5.58; + scene.addChild(lightObj3D); + } +} + +new Sample_TextGeometry().run(); diff --git a/src/assets/Res.ts b/src/assets/Res.ts index b60f37ea..c8e7de3b 100644 --- a/src/assets/Res.ts +++ b/src/assets/Res.ts @@ -165,7 +165,7 @@ export class Res { public async load(url: string, c: Parser, loaderFunctions?: LoaderFunctions) { let loader = new FileLoader(); let parser = await loader.load(url, c, loaderFunctions); - let ret = parser.data; + let ret = parser.data as T["data"]; return ret; } From 5a9ff2084c233e3747ed31909d50dea560d9c4db Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 14 Aug 2024 08:24:11 +0800 Subject: [PATCH 17/25] chore: add README for @orillusion/geometry --- packages/geometry/README.md | 19 +++++++++++++++++++ packages/geometry/package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 packages/geometry/README.md diff --git a/packages/geometry/README.md b/packages/geometry/README.md new file mode 100644 index 00000000..e079292a --- /dev/null +++ b/packages/geometry/README.md @@ -0,0 +1,19 @@ +Extra geometry plugins for [Orillusion](https://www.orillusion.com) + +## Usage +```bash +npm install @orillusion/core --save +npm install @orillusion/geometry --save +``` +```ts +import { Scene3D } from "@orillusion/core" +import { TextGeometry } from "@orillusion/geometry" +``` + +Or access Global build from CDN +```html + + +``` + +More doc from [Orillusion](https://www.orillusion.com/guide/graphics/mesh.html) \ No newline at end of file diff --git a/packages/geometry/package.json b/packages/geometry/package.json index 747bdc16..49d0d30a 100644 --- a/packages/geometry/package.json +++ b/packages/geometry/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/geometry", - "version": "0.2.0", + "version": "0.2.1", "author": "Orillusion", "description": "Orillusion geometry Plugin", "main": "./dist/geometry.umd.js", From ba26dc82b688d67883f042b8434b5e490257ac40 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 14 Aug 2024 18:52:26 +0800 Subject: [PATCH 18/25] refactor: deprecate packages/effect, move Terrain Grass into packages/geometry (#446) --- package.json | 4 +- packages/effect/README.md | 15 --- packages/effect/grass/GrassNode.ts | 5 - packages/effect/index.ts | 5 - packages/effect/package.json | 24 ---- packages/effect/tsconfig.json | 30 ----- packages/effect/vite.config.js | 26 ---- packages/geometry/README.md | 2 +- .../grass}/GrassGeometry.ts | 10 +- .../grass/component/GrassComponent.ts | 7 +- .../grass/material/GrassMaterial.ts | 0 .../grass/shader/GrassBaseShader.ts | 0 .../grass/shader/GrassCastShadowShader.ts | 0 .../grass/shader/GrassShader.ts | 0 .../shader/GrassVertexAttributeShader.ts | 0 packages/geometry/index.ts | 6 +- packages/geometry/package.json | 2 +- .../terrain}/TerrainGeometry.ts | 2 +- packages/geometry/{ => text}/TextGeometry.ts | 8 +- samples/ext/_Sample_Boxes.ts | 117 ------------------ .../Sample_GrassGeometry.ts} | 2 +- .../Sample_TerrainGeometry.ts} | 2 +- 22 files changed, 22 insertions(+), 245 deletions(-) delete mode 100644 packages/effect/README.md delete mode 100644 packages/effect/grass/GrassNode.ts delete mode 100644 packages/effect/index.ts delete mode 100644 packages/effect/package.json delete mode 100644 packages/effect/tsconfig.json delete mode 100644 packages/effect/vite.config.js rename packages/{effect/grass/geometry => geometry/grass}/GrassGeometry.ts (95%) rename packages/{effect => geometry}/grass/component/GrassComponent.ts (83%) rename packages/{effect => geometry}/grass/material/GrassMaterial.ts (100%) rename packages/{effect => geometry}/grass/shader/GrassBaseShader.ts (100%) rename packages/{effect => geometry}/grass/shader/GrassCastShadowShader.ts (100%) rename packages/{effect => geometry}/grass/shader/GrassShader.ts (100%) rename packages/{effect => geometry}/grass/shader/GrassVertexAttributeShader.ts (100%) rename packages/{effect/terrain/geometry => geometry/terrain}/TerrainGeometry.ts (96%) rename packages/geometry/{ => text}/TextGeometry.ts (89%) delete mode 100644 samples/ext/_Sample_Boxes.ts rename samples/{ext/Sample_Grass.ts => geometry/Sample_GrassGeometry.ts} (98%) rename samples/{ext/Sample_Terrain.ts => geometry/Sample_TerrainGeometry.ts} (98%) diff --git a/package.json b/package.json index 3f628424..e5153186 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,13 @@ "dev": "vite", "build": "tsc --p tsconfig.build.json && vite build && npm run build:types && npm run minify", "build:test": "tsc --p tsconfig.build.json && vite build && npm run build:packages", - "build:packages": "npm run build:physics && npm run build:media && npm run build:stats && npm run build:particle && npm run build:graphic && npm run build:effect", + "build:packages": "npm run build:physics && npm run build:media && npm run build:stats && npm run build:particle && npm run build:graphic && npm run build:geometry", "build:physics": "cd packages/physics && npm run build", "build:media": "cd packages/media-extention && npm run build", "build:stats": "cd packages/stats && npm run build", "build:particle": "cd packages/particle && npm run build", "build:graphic": "cd packages/graphic && npm run build", - "build:effect": "cd packages/effect && npm run build", + "build:geometry": "cd packages/geometry && npm run build", "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json && rm -rf dist/packages && mv dist/src dist/types", "minify": "./node_modules/vite/node_modules/.bin/esbuild dist/orillusion.es.max.js --sourcemap --minify --outfile=dist/orillusion.es.js && ./node_modules/vite/node_modules/.bin/esbuild dist/orillusion.umd.max.js --minify --sourcemap --outfile=dist/orillusion.umd.js", "test": "electron test/ci/main.js", diff --git a/packages/effect/README.md b/packages/effect/README.md deleted file mode 100644 index 2dadee67..00000000 --- a/packages/effect/README.md +++ /dev/null @@ -1,15 +0,0 @@ -Add effect components for [Orillusion](https://www.orillusion.com) - -## Install -```bash -npm install @orillusion/core --save -npm install @orillusion/effect --save -``` - -Or access Global build from CDN -```html - - -``` - -More doc from [Orillusion](https://www.orillusion.com/guide/effect/Readme.html) \ No newline at end of file diff --git a/packages/effect/grass/GrassNode.ts b/packages/effect/grass/GrassNode.ts deleted file mode 100644 index 5f77d432..00000000 --- a/packages/effect/grass/GrassNode.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Transform } from "@orillusion/core"; - -export class GrassNode extends Transform { - -} \ No newline at end of file diff --git a/packages/effect/index.ts b/packages/effect/index.ts deleted file mode 100644 index 39a2fef9..00000000 --- a/packages/effect/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./grass/GrassNode"; -export * from "./grass/component/GrassComponent"; -export * from "./grass/geometry/GrassGeometry"; -export * from "./grass/material/GrassMaterial"; -export * from "./terrain/geometry/TerrainGeometry"; diff --git a/packages/effect/package.json b/packages/effect/package.json deleted file mode 100644 index 85bdeeab..00000000 --- a/packages/effect/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@orillusion/effect", - "version": "0.1.3", - "author": "Orillusion", - "description": "Orillusion Effects Plugin", - "main": "./dist/effect.umd.js", - "module": "./dist/effect.es.js", - "module:dev": "./index.ts", - "types": "./dist/index.d.ts", - "files": ["dist"], - "scripts": { - "build": "vite build && npm run build:types && npm run build:clean", - "build:types": "tsc --emitDeclarationOnly -p tsconfig.json", - "build:clean": "mv dist/packages/effect/* dist && rm -rf dist/src && rm -rf dist/packages" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/Orillusion/orillusion.git" - }, - "dependencies": { - "@orillusion/core": "^0.8.0" - } -} diff --git a/packages/effect/tsconfig.json b/packages/effect/tsconfig.json deleted file mode 100644 index 99cc83e4..00000000 --- a/packages/effect/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ESNext", "DOM"], - "moduleResolution": "Node", - "sourceMap": false, - "resolveJsonModule": true, - "esModuleInterop": true, - "types": ["vite/client", "@webgpu/types"], - // for build - // "noEmit": true, - "declaration": true, - "declarationDir": "dist", - "outDir": "dist", - "skipLibCheck": true, - "noImplicitAny": false, - "noUnusedLocals": false, - "noUnusedParameters":false, - "noImplicitReturns": false, - "strictPropertyInitialization": false, - "strictNullChecks": false, - "strict": false, - "paths": { - "@orillusion/core": ["../../src"], - "@orillusion/*": ["../*"] - } - } -} \ No newline at end of file diff --git a/packages/effect/vite.config.js b/packages/effect/vite.config.js deleted file mode 100644 index 1ad90b75..00000000 --- a/packages/effect/vite.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import { defineConfig } from 'vite' -const path = require('path') -export default defineConfig({ - resolve: { - alias: { - '@orillusion/core': path.resolve(__dirname, '../../src'), - '@orillusion': path.resolve(__dirname, '../') - } - }, - build: { - target: 'esnext', - lib: { - entry: path.resolve('index.ts'), - name: 'Effect', - fileName: (format) => `effect.${format}.js` - }, - rollupOptions: { - external: ['@orillusion/core'], - output: { - globals: { - '@orillusion/core': 'Orillusion' - } - } - } - } -}) \ No newline at end of file diff --git a/packages/geometry/README.md b/packages/geometry/README.md index e079292a..4f3f3f23 100644 --- a/packages/geometry/README.md +++ b/packages/geometry/README.md @@ -7,7 +7,7 @@ npm install @orillusion/geometry --save ``` ```ts import { Scene3D } from "@orillusion/core" -import { TextGeometry } from "@orillusion/geometry" +import { TextGeometry, ExtrudeGeometry, TerrainGeometry } from "@orillusion/geometry" ``` Or access Global build from CDN diff --git a/packages/effect/grass/geometry/GrassGeometry.ts b/packages/geometry/grass/GrassGeometry.ts similarity index 95% rename from packages/effect/grass/geometry/GrassGeometry.ts rename to packages/geometry/grass/GrassGeometry.ts index 0289b3b5..4ec4dbfa 100644 --- a/packages/effect/grass/geometry/GrassGeometry.ts +++ b/packages/geometry/grass/GrassGeometry.ts @@ -1,15 +1,11 @@ -import { BoundingBox, GeometryBase, Vector3, VertexAttributeName } from "@orillusion/core"; -import { GrassNode } from "../GrassNode"; - - - +import { BoundingBox, GeometryBase, Transform, Vector3, VertexAttributeName } from "@orillusion/core"; export class GrassGeometry extends GeometryBase { public width: number; public height: number; public segmentW: number; public segmentH: number; - public nodes: GrassNode[]; + public nodes: Transform[]; constructor(width: number, height: number, segmentW: number = 1, segmentH: number = 1, count: number) { super(); @@ -43,7 +39,7 @@ export class GrassGeometry extends GeometryBase { let pi = 3.1415926 * 0.5; for (let gi = 0; gi < count; gi++) { - let node = new GrassNode(); + let node = new Transform(); this.nodes.push(node); let dir = new Vector3(1 * Math.random() - 0.5, 0.0, 1 * Math.random() - 0.5); diff --git a/packages/effect/grass/component/GrassComponent.ts b/packages/geometry/grass/component/GrassComponent.ts similarity index 83% rename from packages/effect/grass/component/GrassComponent.ts rename to packages/geometry/grass/component/GrassComponent.ts index db1e08c4..cbbd4861 100644 --- a/packages/effect/grass/component/GrassComponent.ts +++ b/packages/geometry/grass/component/GrassComponent.ts @@ -1,7 +1,6 @@ -import { BoundingBox, Color, ComponentBase, GPUPrimitiveTopology, LightingFunction_frag, MeshRenderer, Texture, UnLitMaterial, Vector3, VertexAttributeName } from "@orillusion/core"; +import { BoundingBox, Color, ComponentBase, GPUPrimitiveTopology, LightingFunction_frag, MeshRenderer, Texture, Transform, UnLitMaterial, Vector3, VertexAttributeName } from "@orillusion/core"; import { GrassMaterial } from "../material/GrassMaterial"; -import { GrassGeometry } from "../geometry/GrassGeometry"; -import { GrassNode } from "../GrassNode"; +import { GrassGeometry } from "../GrassGeometry"; export class GrassComponent extends MeshRenderer { @@ -40,7 +39,7 @@ export class GrassComponent extends MeshRenderer { this.grassMaterial.baseMap = grassTexture; } - public get nodes(): GrassNode[] { + public get nodes(): Transform[] { return this.grassGeometry.nodes; } } \ No newline at end of file diff --git a/packages/effect/grass/material/GrassMaterial.ts b/packages/geometry/grass/material/GrassMaterial.ts similarity index 100% rename from packages/effect/grass/material/GrassMaterial.ts rename to packages/geometry/grass/material/GrassMaterial.ts diff --git a/packages/effect/grass/shader/GrassBaseShader.ts b/packages/geometry/grass/shader/GrassBaseShader.ts similarity index 100% rename from packages/effect/grass/shader/GrassBaseShader.ts rename to packages/geometry/grass/shader/GrassBaseShader.ts diff --git a/packages/effect/grass/shader/GrassCastShadowShader.ts b/packages/geometry/grass/shader/GrassCastShadowShader.ts similarity index 100% rename from packages/effect/grass/shader/GrassCastShadowShader.ts rename to packages/geometry/grass/shader/GrassCastShadowShader.ts diff --git a/packages/effect/grass/shader/GrassShader.ts b/packages/geometry/grass/shader/GrassShader.ts similarity index 100% rename from packages/effect/grass/shader/GrassShader.ts rename to packages/geometry/grass/shader/GrassShader.ts diff --git a/packages/effect/grass/shader/GrassVertexAttributeShader.ts b/packages/geometry/grass/shader/GrassVertexAttributeShader.ts similarity index 100% rename from packages/effect/grass/shader/GrassVertexAttributeShader.ts rename to packages/geometry/grass/shader/GrassVertexAttributeShader.ts diff --git a/packages/geometry/index.ts b/packages/geometry/index.ts index eda175ee..eef3f1de 100644 --- a/packages/geometry/index.ts +++ b/packages/geometry/index.ts @@ -7,4 +7,8 @@ export * from "./ExtrudeGeometry/Path2D" export * from "./ExtrudeGeometry/Shape2D" export * from "./ExtrudeGeometry/ShapeUtils" export * from "./parser/FontParser" -export * from "./TextGeometry" \ No newline at end of file +export * from "./text/TextGeometry" +export * from "./terrain/TerrainGeometry" +export * from "./grass/GrassGeometry" +export * from "./grass/component/GrassComponent"; +export * from "./grass/material/GrassMaterial"; \ No newline at end of file diff --git a/packages/geometry/package.json b/packages/geometry/package.json index 49d0d30a..cf323957 100644 --- a/packages/geometry/package.json +++ b/packages/geometry/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/geometry", - "version": "0.2.1", + "version": "0.2.2", "author": "Orillusion", "description": "Orillusion geometry Plugin", "main": "./dist/geometry.umd.js", diff --git a/packages/effect/terrain/geometry/TerrainGeometry.ts b/packages/geometry/terrain/TerrainGeometry.ts similarity index 96% rename from packages/effect/terrain/geometry/TerrainGeometry.ts rename to packages/geometry/terrain/TerrainGeometry.ts index 4cfd3b7e..d602c051 100644 --- a/packages/effect/terrain/geometry/TerrainGeometry.ts +++ b/packages/geometry/terrain/TerrainGeometry.ts @@ -1,4 +1,4 @@ -import { BitmapTexture2D, Plane, PlaneGeometry, Texture, Vector3, VertexAttributeName, lerp } from "@orillusion/core" +import { BitmapTexture2D, PlaneGeometry, Vector3, VertexAttributeName, lerp } from "@orillusion/core" export class TerrainGeometry extends PlaneGeometry { diff --git a/packages/geometry/TextGeometry.ts b/packages/geometry/text/TextGeometry.ts similarity index 89% rename from packages/geometry/TextGeometry.ts rename to packages/geometry/text/TextGeometry.ts index 834c2339..a1372b25 100644 --- a/packages/geometry/TextGeometry.ts +++ b/packages/geometry/text/TextGeometry.ts @@ -1,7 +1,7 @@ -import { Shape2D } from "./ExtrudeGeometry/Shape2D"; -import { ExtrudeGeometry, ExtrudeGeometryArgs } from "./ExtrudeGeometry/ExtrudeGeometry"; -import { ShapeUtils } from "./ExtrudeGeometry/ShapeUtils"; -import { Font } from "./lib/opentype"; +import { Shape2D } from "../ExtrudeGeometry/Shape2D"; +import { ExtrudeGeometry, ExtrudeGeometryArgs } from "../ExtrudeGeometry/ExtrudeGeometry"; +import { ShapeUtils } from "../ExtrudeGeometry/ShapeUtils"; +import { Font } from "../lib/opentype"; export type TextGeometryArgs = ExtrudeGeometryArgs & { font: Font; diff --git a/samples/ext/_Sample_Boxes.ts b/samples/ext/_Sample_Boxes.ts deleted file mode 100644 index 2336ff6e..00000000 --- a/samples/ext/_Sample_Boxes.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { GUIHelp } from "@orillusion/debug/GUIHelp"; -import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, KelvinUtil, PlaneGeometry, VertexAttributeName, LitMaterial, MeshRenderer, Vector4, Vector3, Matrix3, PostProcessingComponent, TAAPost, BitmapTexture2D, GlobalFog, Color, BoxGeometry, UnLitMaterial, PointLight, GTAOPost, BloomPost } from "@orillusion/core"; -import { GUIUtil } from "@samples/utils/GUIUtil"; -import { GrassComponent, TerrainGeometry } from "@orillusion/effect"; - -// An sample of custom vertex attribute of geometry -class Sample_Boxes { - view: View3D; - post: PostProcessingComponent; - async run() { - Engine3D.setting.shadow.enable = true; - Engine3D.setting.shadow.updateFrameRate = 1; - Engine3D.setting.shadow.shadowBound = 500; - Engine3D.setting.shadow.autoUpdate = true; - Engine3D.setting.shadow.shadowSize = 1024; - // Engine3D.setting.render.zPrePass = true; - - GUIHelp.init(); - - await Engine3D.init(); - this.view = new View3D(); - this.view.scene = new Scene3D(); - this.view.scene.addComponent(AtmosphericComponent); - - this.view.camera = CameraUtil.createCamera3DObject(this.view.scene); - this.view.camera.enableCSM = true; - this.view.camera.perspective(60, webGPUContext.aspect, 1, 50000.0); - this.view.camera.object3D.z = -15; - this.view.camera.object3D.addComponent(HoverCameraController).setCamera(35, -20, 1000); - - Engine3D.startRenderView(this.view); - - this.post = this.view.scene.addComponent(PostProcessingComponent); - this.post.addPost(GTAOPost); - this.post.addPost(BloomPost); - let fog = this.post.addPost(GlobalFog); - fog.start = 91.0862; - fog.end = 487.5528; - fog.fogHeightScale = 0.0141; - fog.density = 0.2343; - fog.ins = 0.1041; - fog.skyFactor = 0.5316; - fog.overrideSkyFactor = 0.025; - - fog.fogColor = new Color(136 / 255, 215 / 255, 236 / 255, 1); - fog.fogHeightScale = 0.1; - fog.falloff = 2.5; - fog.scatteringExponent = 7.196; - fog.dirHeightLine = 6.5; - - GUIUtil.renderGlobalFog(fog); - - this.createScene(this.view.scene); - } - - private async createScene(scene: Scene3D) { - { - let sunObj = new Object3D(); - let sunLight = sunObj.addComponent(DirectLight); - sunLight.lightColor = KelvinUtil.color_temperature_to_rgb(6553); - sunLight.castShadow = true; - sunLight.intensity = 3; - sunObj.transform.rotationX = 50; - sunObj.transform.rotationY = 50; - GUIUtil.renderDirLight(sunLight); - scene.addChild(sunObj); - } - - { - let geometry = new BoxGeometry(5, 200, 5); - let litMaterial = new LitMaterial(); - // let litMaterial = new UnLitMaterial(); - let w = 30; - let h = 30; - for (let i = 0; i < w; i++) { - for (let j = 0; j < h; j++) { - let obj = new Object3D(); - let mr = obj.addComponent(MeshRenderer); - mr.material = litMaterial; - mr.geometry = geometry; - obj.x = i * 10 - w * 0.5 * 10; - obj.y = Math.random() * 100; - obj.z = j * 10 - h * 0.5 * 10; - scene.addChild(obj); - } - } - - let obj = new Object3D(); - let mr = obj.addComponent(MeshRenderer); - mr.material = litMaterial; - mr.geometry = geometry; - obj.localScale = new Vector3(1000, 1, 1000); - scene.addChild(obj); - } - - { - // for (let j = 0; j < 100; j++) { - // const lightObj = new Object3D();; - // let pointLight = lightObj.addComponent(PointLight); - // pointLight.castShadow = false; - // lightObj.transform.x = Math.random() * 100 * 10 - 100 * 10 * 0.5; - // lightObj.transform.y = 15; - // lightObj.transform.z = Math.random() * 100 * 10 - 100 * 10 * 0.5; - // pointLight.range = 100; - // pointLight.intensity = 60; - // scene.addChild(lightObj); - // } - } - - - // let globalFog = this.post.getPost(GlobalFog); - // GUIUtil.renderGlobalFog(globalFog); - } - -} - -new Sample_Boxes().run(); \ No newline at end of file diff --git a/samples/ext/Sample_Grass.ts b/samples/geometry/Sample_GrassGeometry.ts similarity index 98% rename from samples/ext/Sample_Grass.ts rename to samples/geometry/Sample_GrassGeometry.ts index 3e62d6b4..3d82a912 100644 --- a/samples/ext/Sample_Grass.ts +++ b/samples/geometry/Sample_GrassGeometry.ts @@ -1,7 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, KelvinUtil, PlaneGeometry, VertexAttributeName, LitMaterial, MeshRenderer, Vector4, Vector3, Matrix3, PostProcessingComponent, TAAPost, BitmapTexture2D, GlobalFog, Color, FXAAPost } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; -import { GrassComponent, TerrainGeometry } from "@orillusion/effect"; +import { GrassComponent, TerrainGeometry } from "@orillusion/geometry"; import { Stats } from "@orillusion/stats"; // An sample of custom vertex attribute of geometry diff --git a/samples/ext/Sample_Terrain.ts b/samples/geometry/Sample_TerrainGeometry.ts similarity index 98% rename from samples/ext/Sample_Terrain.ts rename to samples/geometry/Sample_TerrainGeometry.ts index 031266ed..9cc33726 100644 --- a/samples/ext/Sample_Terrain.ts +++ b/samples/geometry/Sample_TerrainGeometry.ts @@ -1,7 +1,7 @@ import { GUIHelp } from "@orillusion/debug/GUIHelp"; import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, KelvinUtil, PlaneGeometry, VertexAttributeName, LitMaterial, MeshRenderer, Vector4, Vector3, Matrix3, PostProcessingComponent, TAAPost, BitmapTexture2D, GlobalFog, Color, FXAAPost } from "@orillusion/core"; import { GUIUtil } from "@samples/utils/GUIUtil"; -import { GrassComponent, TerrainGeometry } from "@orillusion/effect"; +import { TerrainGeometry } from "@orillusion/geometry"; // An sample of custom vertex attribute of geometry class Sample_Terrain { From c154fe14bea2782af7645a051802fbb997622bd1 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 14 Aug 2024 19:27:45 +0800 Subject: [PATCH 19/25] chore: fix sample import --- samples/physics/Sample_MultipleShapes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/physics/Sample_MultipleShapes.ts b/samples/physics/Sample_MultipleShapes.ts index 653c3640..edd5a851 100644 --- a/samples/physics/Sample_MultipleShapes.ts +++ b/samples/physics/Sample_MultipleShapes.ts @@ -1,5 +1,5 @@ import { Engine3D, LitMaterial, MeshRenderer, BoxGeometry, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, SphereGeometry, CameraUtil, HoverCameraController, BitmapTexture2D, VertexAttributeName, Color, CylinderGeometry, TorusGeometry, ComponentBase } from "@orillusion/core"; -import { TerrainGeometry } from "@orillusion/effect"; +import { TerrainGeometry } from "@orillusion/geometry"; import { Graphic3D } from "@orillusion/graphic"; import { Ammo, CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics"; import { Stats } from "@orillusion/stats"; From 9d8ae6bd71c0a67e8f7f4f0d9fd92f66b436f6e3 Mon Sep 17 00:00:00 2001 From: Codeboy Date: Sun, 18 Aug 2024 21:15:50 +0800 Subject: [PATCH 20/25] Fix: depthCompare DepthCompare cant work properly --- src/materials/Material.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/materials/Material.ts b/src/materials/Material.ts index 3247f765..9c57ddf2 100644 --- a/src/materials/Material.ts +++ b/src/materials/Material.ts @@ -96,6 +96,11 @@ export class Material { public set depthCompare(value: GPUCompareFunction) { this._defaultSubShader.depthCompare = value; + for (let item of this._shader.passShader.values()) { + for (let s of item) { + s.depthCompare = value; + } + } } @@ -137,7 +142,7 @@ export class Material { this._defaultSubShader.setDefine("USE_BILLBOARD", value); } - public get topology(){ + public get topology() { return this._defaultSubShader.topology; } From 23868d04d87c1406641eb46eb9cdf4e893aac111 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Mon, 19 Aug 2024 18:27:48 +0800 Subject: [PATCH 21/25] chore: use peerDependencies in packages fix multi version dependencies use local @orillsuion/core as peer dependency --- package.json | 2 +- packages/geometry/package.json | 8 ++++---- packages/graphic/package.json | 8 ++++---- packages/media-extention/package.json | 6 +++--- packages/particle/package.json | 6 +++--- packages/physics/package.json | 8 ++++---- packages/stats/package.json | 6 +++--- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index e5153186..c7e99ea7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/core", - "version": "0.8.3-dev.2", + "version": "0.8.3-dev.3", "author": "Orillusion", "description": "Orillusion WebGPU Engine", "type": "module", diff --git a/packages/geometry/package.json b/packages/geometry/package.json index cf323957..05218faf 100644 --- a/packages/geometry/package.json +++ b/packages/geometry/package.json @@ -1,8 +1,8 @@ { "name": "@orillusion/geometry", - "version": "0.2.2", + "version": "0.2.4", "author": "Orillusion", - "description": "Orillusion geometry Plugin", + "description": "Orillusion Geometry Plugin", "main": "./dist/geometry.umd.js", "module": "./dist/geometry.es.js", "module:dev": "./index.ts", @@ -21,7 +21,7 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/core": "^0.8.0" + "peerDependencies": { + "@orillusion/core": ">=0.8.0" } } diff --git a/packages/graphic/package.json b/packages/graphic/package.json index 9ed99e18..08f4db6e 100644 --- a/packages/graphic/package.json +++ b/packages/graphic/package.json @@ -1,8 +1,8 @@ { "name": "@orillusion/graphic", - "version": "0.2.0", + "version": "0.2.2", "author": "Orillusion", - "description": "Orillusion graphic Plugin", + "description": "Orillusion Graphic Plugin", "main": "./dist/graphic.umd.js", "module": "./dist/graphic.es.js", "module:dev": "./index.ts", @@ -21,7 +21,7 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/core": "^0.8.0" + "peerDependencies": { + "@orillusion/core": ">=0.8.0" } } diff --git a/packages/media-extention/package.json b/packages/media-extention/package.json index 6a2a7225..9ffc67e7 100644 --- a/packages/media-extention/package.json +++ b/packages/media-extention/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/media-extention", - "version": "0.3.4", + "version": "0.3.6", "author": "Orillusion", "description": "Orillusion Media Material Extention", "main": "./dist/media.umd.js", @@ -20,7 +20,7 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/core": "^0.7.0" + "peerDependencies": { + "@orillusion/core": ">=0.8.0" } } diff --git a/packages/particle/package.json b/packages/particle/package.json index e63c7790..b292b79b 100644 --- a/packages/particle/package.json +++ b/packages/particle/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/particle", - "version": "0.1.0", + "version": "0.1.6", "author": "Orillusion", "description": "Orillusion Particle Plugin", "main": "./dist/particle.umd.js", @@ -20,7 +20,7 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/core": "^0.7.0" + "peerDependencies": { + "@orillusion/core": ">=0.8.0" } } diff --git a/packages/physics/package.json b/packages/physics/package.json index a270ddc0..a5a9a1f2 100644 --- a/packages/physics/package.json +++ b/packages/physics/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/physics", - "version": "0.3.0", + "version": "0.3.2", "author": "Orillusion", "description": "Orillusion Physics Plugin, Powerd by Ammo.js", "main": "./dist/physics.umd.js", @@ -20,8 +20,8 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/ammo": "^0.2.1", - "@orillusion/core": "^0.8.0" + "peerDependencies": { + "@orillusion/ammo": ">=0.2.1", + "@orillusion/core": ">=0.8.0" } } diff --git a/packages/stats/package.json b/packages/stats/package.json index 9147f621..ce1ea61e 100644 --- a/packages/stats/package.json +++ b/packages/stats/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/stats", - "version": "0.2.2", + "version": "0.2.6", "author": "Orillusion", "description": "Orillusion Stats Plugin", "main": "./dist/stats.umd.js", @@ -20,7 +20,7 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, - "dependencies": { - "@orillusion/core": "^0.7.0" + "peerDependencies": { + "@orillusion/core": ">=0.8.0" } } From 4529594491e111e2d98ca4319189215614d97654 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Mon, 26 Aug 2024 07:21:09 +0800 Subject: [PATCH 22/25] fix: fix InstanceDraw destroy error update instancedraw samples --- ...Instance.ts => Sample_InstancedrawCall.ts} | 28 +++-------- ...ereDraw.ts => Sample_InstancedrawCall2.ts} | 49 ++++++++++--------- ...allShareGeometry.ts => Sample_drawCall.ts} | 34 ++++--------- samples/index.ts | 13 ----- src/assets/shader/materials/Lambert_shader.ts | 2 +- src/assets/shader/materials/UnLit.ts | 2 +- .../shader/materials/UnLitTextureArray.ts | 2 +- .../renderer/InstanceDrawComponent.ts | 25 +++++++--- src/core/geometry/GeometryBase.ts | 4 -- .../parser/prefab/mats/shader/UnLitShader.ts | 1 - src/materials/Material.ts | 4 ++ 11 files changed, 66 insertions(+), 98 deletions(-) rename samples/benchmark/{Sample_drawCallInstance.ts => Sample_InstancedrawCall.ts} (88%) rename samples/benchmark/{Sample_SphereDraw.ts => Sample_InstancedrawCall2.ts} (72%) rename samples/benchmark/{Sample_drawCallShareGeometry.ts => Sample_drawCall.ts} (81%) diff --git a/samples/benchmark/Sample_drawCallInstance.ts b/samples/benchmark/Sample_InstancedrawCall.ts similarity index 88% rename from samples/benchmark/Sample_drawCallInstance.ts rename to samples/benchmark/Sample_InstancedrawCall.ts index e2ecb9c3..ca564a5b 100644 --- a/samples/benchmark/Sample_drawCallInstance.ts +++ b/samples/benchmark/Sample_InstancedrawCall.ts @@ -1,7 +1,6 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; import { Stats } from '@orillusion/stats' import { Engine3D, Scene3D, AtmosphericComponent, CameraUtil, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, KelvinUtil, View3D, Vector3, Vector3Ex, UnLitMaterial, InstanceDrawComponent, LambertMaterial, Time, BoundingBox, Color, OcclusionSystem, PostProcessingComponent, GlobalFog, SphereGeometry } from '@orillusion/core'; -import { GUIUtil } from '@samples/utils/GUIUtil'; // simple base demo class Sample_drawCallInstance { @@ -54,17 +53,13 @@ class Sample_drawCallInstance { // start render Engine3D.startRenderView(view); GUIHelp.init(); - - GUIHelp.add(this, "anim").onChange = () => { - this.anim != this.anim; - }; - + GUIHelp.open(); + GUIHelp.add(this, "anim").onChange = () => this.anim != this.anim; this.initScene(); } private _list: Object3D[] = []; - private _rotList: number[] = []; initScene() { { this.lightObj3D = new Object3D(); @@ -79,8 +74,6 @@ class Sample_drawCallInstance { directLight.castShadow = true; directLight.intensity = 30; directLight.indirect = 1; - GUIHelp.init(); - GUIUtil.renderDirLight(directLight); this.scene.addChild(this.lightObj3D); } @@ -95,10 +88,7 @@ class Sample_drawCallInstance { let group = new Object3D(); let count = 10 * 10000; - // let count = 200; - GUIHelp.addFolder('info'); - GUIHelp.open(); GUIHelp.addLabel(`use instance draw box`); GUIHelp.addInfo(`count `, count); @@ -123,9 +113,8 @@ class Sample_drawCallInstance { obj.transform.rotationX = Math.random() * 360; obj.transform.rotationY = Math.random() * 360; obj.transform.rotationZ = Math.random() * 360; - - this._rotList.push((Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 100); - + + // use localDetailRot to update rotation by time obj.transform.localDetailRot = new Vector3( (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 50 * 0.001, (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 50 * 0.001, @@ -136,9 +125,8 @@ class Sample_drawCallInstance { } group.addComponent(InstanceDrawComponent); - group.transform.localDetailRot = new Vector3(0, 1.0 * 0.001, 0); - this._rotList.push(1.0); - + // use localDetailRot to update rotation by time + group.transform.localDetailRot = new Vector3(0, 0.01, 0); group.bound = new BoundingBox(Vector3.SAFE_MIN, Vector3.SAFE_MAX); this._list.push(group); this.scene.addChild(group); @@ -147,10 +135,8 @@ class Sample_drawCallInstance { renderLoop() { if (this.anim) { - let i = 0; - for (i = 0; i < this._list.length; i++) { + for (let i = 0; i < this._list.length; i++) { let element = this._list[i]; - // element.transform.rotationY += 1; element.transform.localChange = true; } } diff --git a/samples/benchmark/Sample_SphereDraw.ts b/samples/benchmark/Sample_InstancedrawCall2.ts similarity index 72% rename from samples/benchmark/Sample_SphereDraw.ts rename to samples/benchmark/Sample_InstancedrawCall2.ts index 6742c58a..1fe421ab 100644 --- a/samples/benchmark/Sample_SphereDraw.ts +++ b/samples/benchmark/Sample_InstancedrawCall2.ts @@ -1,7 +1,6 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; import { Stats } from '@orillusion/stats' import { Engine3D, Scene3D, AtmosphericComponent, CameraUtil, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, KelvinUtil, View3D, Vector3, Vector3Ex, UnLitMaterial, InstanceDrawComponent, LambertMaterial, Time, BoundingBox, Color } from '@orillusion/core'; -import { GUIUtil } from '@samples/utils/GUIUtil'; // simple base demo class Sample_SphereDraw { @@ -28,7 +27,7 @@ class Sample_SphereDraw { // add a basic camera controller let hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController); - hoverCameraController.setCamera(15, -15, 100); + hoverCameraController.setCamera(15, -15, 200); // add a basic direct light let lightObj = new Object3D(); @@ -51,11 +50,10 @@ class Sample_SphereDraw { Engine3D.startRenderView(view); GUIHelp.init(); - + GUIHelp.open(); GUIHelp.add(this, "anim").onChange = () => { this.anim != this.anim; }; - this.initScene(); } @@ -63,28 +61,27 @@ class Sample_SphereDraw { private _list: Object3D[] = []; initScene() { let shareGeometry = new BoxGeometry(); - let materials = [ - new LambertMaterial() - ]; - - for (let i = 0; i < materials.length; i++) { - const element = materials[i]; - element.baseColor = Color.random(); + let materials = []; + for (let i = 0; i < 1000; i++) { + let mat = new UnLitMaterial() + mat.baseColor = Color.random(); + materials.push(mat) } - // let material = new LitMaterial(); - let group = new Object3D(); this.scene.addChild(group); - // let count = 150000; - let count = 10000; + let count = 100000; + + GUIHelp.addLabel(`instance draw with multi materials`); + GUIHelp.addInfo(`object count `, count); + GUIHelp.addInfo(`material count`, materials.length) + for (let i = 0; i < count; i++) { let pos = Vector3Ex.sphere(100); - // let pos = Vector3Ex.getRandomXYZ(-2, 2); let obj = new Object3D(); let mr = obj.addComponent(MeshRenderer); mr.geometry = shareGeometry; - mr.material = materials[Math.floor(Math.random() * materials.length)]; + mr.material = materials[i % materials.length]; obj.localPosition = pos; group.addChild(obj); this._list.push(obj); @@ -98,20 +95,26 @@ class Sample_SphereDraw { obj.transform.scaleZ = Math.random() * 5 + 1; obj.transform.forward = d; - obj["rot"] = (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 20; + + obj.transform.localDetailRot = new Vector3( + (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 50 * 0.001, + (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 50 * 0.001, + (Math.random() * 1 - 1 * 0.5) * 2.0 * Math.random() * 50 * 0.001); } group.addComponent(InstanceDrawComponent); - group["rot"] = 1.0; + // use localDetailRot to update rotation by time + group.transform.localDetailRot = new Vector3(0, 0.01, 0); group.bound = new BoundingBox(Vector3.SAFE_MIN, Vector3.SAFE_MAX); this._list.push(group); } renderLoop() { if (this.anim) { - this._list[this._list.length - 1].rotationY += Time.delta * 0.01; - this._list.forEach((v) => { - // v.transform.rotationY += Time.delta * 0.01 * v["rot"]; - }) + for (let i = 0; i < this._list.length; i++) { + let element = this._list[i]; + element.transform.localChange = true; + } + // this._list[this._list.length - 1].transform.rotationY += 0.1 } } } diff --git a/samples/benchmark/Sample_drawCallShareGeometry.ts b/samples/benchmark/Sample_drawCall.ts similarity index 81% rename from samples/benchmark/Sample_drawCallShareGeometry.ts rename to samples/benchmark/Sample_drawCall.ts index d3bc674b..618f3b7e 100644 --- a/samples/benchmark/Sample_drawCallShareGeometry.ts +++ b/samples/benchmark/Sample_drawCall.ts @@ -1,7 +1,6 @@ import { GUIHelp } from '@orillusion/debug/GUIHelp'; import { Stats } from '@orillusion/stats' import { Engine3D, Scene3D, AtmosphericComponent, CameraUtil, HoverCameraController, Object3D, MeshRenderer, BoxGeometry, LitMaterial, DirectLight, KelvinUtil, View3D, Vector3, Vector3Ex, UnLitMaterial, InstanceDrawComponent, LambertMaterial, Time, BoundingBox, Color, OcclusionSystem, PostProcessingComponent, GlobalFog, SphereGeometry, RendererMask, RenderLayer } from '@orillusion/core'; -import { GUIUtil } from '@samples/utils/GUIUtil'; // simple base demo export class Sample_drawCallShareGeometry { @@ -66,23 +65,14 @@ export class Sample_drawCallShareGeometry { private _rotList: number[] = []; initScene() { let shareGeometry = new BoxGeometry(); - - let mats = []; - for (let i = 0; i < 1; i++) { - const mat = new LambertMaterial(); - mat.baseColor = new Color( - Math.random() * 0.85, - Math.random() * 0.85, - Math.random() * 0.85, - ) - - // mat.baseColor = new Color().hexToRGB(0xcccccc) - mats.push(mat); - } - - + const mat = new LambertMaterial(); + mat.baseColor = new Color( + Math.random() * 0.85, + Math.random() * 0.85, + Math.random() * 0.85, + ) let group = new Object3D(); - let count = 5 * 10000; + let count = 10 * 10000; GUIHelp.addFolder('info'); GUIHelp.open(); @@ -90,15 +80,12 @@ export class Sample_drawCallShareGeometry { GUIHelp.addInfo(`count `, count); let ii = 0; - // let count = 70000; for (let i = 0; i < count; i++) { let pos = Vector3Ex.sphereXYZ(ii * 60 + 20, ii * 60 + 100, 100, i * 0.001 + 10, 100); - // let pos = Vector3Ex.getRandomXYZ(-2, 2); let obj = new Object3D(); let mr = obj.addComponent(MeshRenderer); - // mr.renderLayer = RenderLayer.DynamicBatch; mr.geometry = shareGeometry; - mr.material = mats[Math.floor(Math.random() * mats.length)]; + mr.material = mat; obj.localPosition = pos; group.addChild(obj); this._list.push(obj); @@ -122,8 +109,7 @@ export class Sample_drawCallShareGeometry { } } - // group.addComponent(InstanceDrawComponent); - group.transform.localDetailRot = new Vector3(0, 1.0 * 0.001 * 0.15, 0); + group.transform.localDetailRot = new Vector3(0, 0.01, 0); this._rotList.push(1.0 * 0.35); group.bound = new BoundingBox(Vector3.SAFE_MIN, Vector3.SAFE_MAX); @@ -136,8 +122,6 @@ export class Sample_drawCallShareGeometry { let i = 0; for (let i = 0; i < this._list.length; i++) { const element = this._list[i]; - // element.transform.rotationY += Time.delta * 0.01 * this._rotList[i]; - // element.transform._localRot.y += Time.delta * 0.01 * this._rotList[i]; element.transform.localChange = true; } } diff --git a/samples/index.ts b/samples/index.ts index a66a72eb..a65bfb48 100644 --- a/samples/index.ts +++ b/samples/index.ts @@ -1,16 +1,3 @@ -import { Sample_AnimCurve } from "./animation/Sample_CurveAnimation"; -import { Sample_PointLight } from "./lights/Sample_PointLight"; -import { Sample_PointLightShadow } from "./lights/Sample_PointLightShadow"; -import { Sample_CarPaint } from "./material/Sample_CarPaint"; -import { Sample_SSGI } from "./post/Sample_SSGI"; - -// new Sample_CarPaint().run(); -// new Sample_SSGI().run(); - -// new Sample_AnimCurve().run(); - -// new Sample_PointLightShadow().run(); - /******** Load all samples in /src/sample/ ********/ { // find all demos in /sample diff --git a/src/assets/shader/materials/Lambert_shader.ts b/src/assets/shader/materials/Lambert_shader.ts index 7271438c..d6d8a4e1 100644 --- a/src/assets/shader/materials/Lambert_shader.ts +++ b/src/assets/shader/materials/Lambert_shader.ts @@ -24,7 +24,7 @@ export let Lambert_shader: string = /*wgsl*/ ` var uv = transformUV1.zw * ORI_VertexVarying.fragUV0 + transformUV1.xy; let baseColor = textureSample(baseMap,baseMapSampler,uv) ; - if(baseColor.w < 0.5){ + if(baseColor.w < materialUniform.alphaCutoff){ discard ; } diff --git a/src/assets/shader/materials/UnLit.ts b/src/assets/shader/materials/UnLit.ts index 05971fd0..8b311e13 100644 --- a/src/assets/shader/materials/UnLit.ts +++ b/src/assets/shader/materials/UnLit.ts @@ -32,7 +32,7 @@ export let UnLit: string = /*wgsl*/ ` var uv = transformUV1.zw * ORI_VertexVarying.fragUV0 + transformUV1.xy; let color = textureSample(baseMap,baseMapSampler,uv) ; - if(color.w < 0.5){ + if(color.w < materialUniform.alphaCutoff){ discard ; } diff --git a/src/assets/shader/materials/UnLitTextureArray.ts b/src/assets/shader/materials/UnLitTextureArray.ts index 85b20e41..e915a528 100644 --- a/src/assets/shader/materials/UnLitTextureArray.ts +++ b/src/assets/shader/materials/UnLitTextureArray.ts @@ -89,7 +89,7 @@ export let UnLitTextureArray: string = /*wgsl*/ ` // irradiance = LinearToGammaSpace(irradiance.rgb) * color.rgb ;//* att ; color += graphicNode.emissiveColor ; - if(color.w < 0.5){ + if(color.w < materialUniform.alphaCutoff){ discard ; } diff --git a/src/components/renderer/InstanceDrawComponent.ts b/src/components/renderer/InstanceDrawComponent.ts index f1d61ee8..242a20f0 100644 --- a/src/components/renderer/InstanceDrawComponent.ts +++ b/src/components/renderer/InstanceDrawComponent.ts @@ -1,6 +1,5 @@ import { GPUContext } from "../../gfx/renderJob/GPUContext"; import { RTResourceMap } from "../../gfx/renderJob/frame/RTResourceMap"; -import { ClusterLightingRender } from "../../gfx/renderJob/passRenderer/cluster/ClusterLightingRender"; import { RenderContext } from "../../gfx/renderJob/passRenderer/RenderContext"; import { MeshRenderer } from "./MeshRenderer"; import { RenderNode } from "./RenderNode"; @@ -9,6 +8,7 @@ import { View3D } from "../../core/View3D"; import { RendererPassState } from "../../gfx/renderJob/passRenderer/state/RendererPassState"; import { PassType } from "../../gfx/renderJob/passRenderer/state/PassType"; import { ClusterLightingBuffer } from "../../gfx/renderJob/passRenderer/cluster/ClusterLightingBuffer"; +import { ComponentCollect } from "../../gfx/renderJob/collect/ComponentCollect"; export class InstanceDrawComponent extends RenderNode { @@ -64,10 +64,14 @@ export class InstanceDrawComponent extends RenderNode { }) } - public stop(): void { - + public reset(){ + if(this._keyRenderGroup.size > 0){ + this._keyRenderGroup.clear() + this._keyBufferGroup.clear() + this._keyIdsGroup.clear() + this.start() + } } - public nodeUpdate(view: View3D, passType: PassType, renderPassState: RendererPassState, clusterLightingBuffer?: ClusterLightingBuffer): void { this._keyRenderGroup.forEach((v, k) => { let instanceMatrixBuffer = this._keyBufferGroup.get(k); @@ -107,11 +111,7 @@ export class InstanceDrawComponent extends RenderNode { continue; for (let j = 0; j < passes.length; j++) { - if (!passes || passes.length == 0) - continue; let matPass = passes[j]; - // if (!matPass.enable) - // continue; GPUContext.bindGeometryBuffer(renderContext.encoder, renderNode.geometry); const renderShader = matPass; @@ -138,4 +138,13 @@ export class InstanceDrawComponent extends RenderNode { } } } + + public beforeDestroy(force?: boolean): void { + this._keyRenderGroup.clear(); + this._keyBufferGroup.clear(); + this._keyIdsGroup.clear(); + //@ts-ignore + this._keyRenderGroup = this._keyBufferGroup = this._keyIdsGroup = undefined; + ComponentCollect.removeWaitStart(this.object3D, this); + } } diff --git a/src/core/geometry/GeometryBase.ts b/src/core/geometry/GeometryBase.ts index b8b18ce7..3912d00a 100644 --- a/src/core/geometry/GeometryBase.ts +++ b/src/core/geometry/GeometryBase.ts @@ -57,12 +57,8 @@ export class GeometryBase { constructor() { this.instanceID = UUID(); - this._attributeMap = new Map(); this._attributes = []; - - - this._vertexBuffer = new GeometryVertexBuffer(); } diff --git a/src/loader/parser/prefab/mats/shader/UnLitShader.ts b/src/loader/parser/prefab/mats/shader/UnLitShader.ts index 4ccd4ce5..d0d94212 100644 --- a/src/loader/parser/prefab/mats/shader/UnLitShader.ts +++ b/src/loader/parser/prefab/mats/shader/UnLitShader.ts @@ -1,4 +1,3 @@ -import { Engine3D } from "../../../../../Engine3D"; import { GPUCullMode } from "../../../../../gfx/graphics/webGpu/WebGPUConst"; import { Texture } from "../../../../../gfx/graphics/webGpu/core/texture/Texture"; import { RenderShaderPass } from "../../../../../gfx/graphics/webGpu/shader/RenderShaderPass"; diff --git a/src/materials/Material.ts b/src/materials/Material.ts index 9c57ddf2..4eaccba7 100644 --- a/src/materials/Material.ts +++ b/src/materials/Material.ts @@ -9,6 +9,7 @@ import { Vector2 } from "../math/Vector2"; import { Vector3 } from "../math/Vector3"; import { Vector4 } from "../math/Vector4"; import { BlendMode } from "./BlendMode"; +import { UUID } from "../util/Global"; export class Material { @@ -31,6 +32,7 @@ export class Material { protected _shader: Shader; constructor() { + this.instanceID = UUID(); } public set shader(shader: Shader) { @@ -179,6 +181,8 @@ export class Material { destroy(force: boolean) { + this.name = null; + this.instanceID = null; this._shader.destroy(); this._shader = null; } From 452d730ef3377867cd81fe6d78e3a1b744c4e2b5 Mon Sep 17 00:00:00 2001 From: MorenID <51897387+ID-Emmett@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:41:54 +0800 Subject: [PATCH 23/25] feat(physics): add RopeSoftBody, rigidbody dragger, and enhance collisionShapeUtil (#448) --- packages/physics/Physics.ts | 19 +- packages/physics/index.ts | 1 + packages/physics/package.json | 6 +- packages/physics/rigidbody/Rigidbody.ts | 8 +- packages/physics/softbody/ClothSoftbody.ts | 409 ++++++------------ packages/physics/softbody/RopeSoftbody.ts | 200 +++++++++ packages/physics/softbody/SoftbodyBase.ts | 170 ++++++++ packages/physics/utils/CollisionShapeUtil.ts | 201 +++++++-- packages/physics/utils/PhysicsDragger.ts | 197 +++++++++ samples/physics/Sample_Cloth.ts | 110 +++++ samples/physics/Sample_Dominoes.ts | 159 ++++--- samples/physics/Sample_MultipleConstraints.ts | 137 +++--- samples/physics/Sample_PhysicsBox.ts | 2 +- samples/physics/Sample_Rope.ts | 129 ++++++ samples/physics/Sample_dofSpringConstraint.ts | 11 +- 15 files changed, 1300 insertions(+), 459 deletions(-) create mode 100644 packages/physics/softbody/RopeSoftbody.ts create mode 100644 packages/physics/softbody/SoftbodyBase.ts create mode 100644 packages/physics/utils/PhysicsDragger.ts create mode 100644 samples/physics/Sample_Cloth.ts create mode 100644 samples/physics/Sample_Rope.ts diff --git a/packages/physics/Physics.ts b/packages/physics/Physics.ts index fa16f89c..3630aa45 100644 --- a/packages/physics/Physics.ts +++ b/packages/physics/Physics.ts @@ -6,6 +6,7 @@ import { TempPhyMath } from './utils/TempPhyMath'; import { Rigidbody } from './rigidbody/Rigidbody'; import { PhysicsDebugDrawer } from './visualDebug/PhysicsDebugDrawer'; import { DebugDrawerOptions } from './visualDebug/DebugDrawModeEnum'; +import { PhysicsDragger } from './utils/PhysicsDragger' class _Physics { private _world: Ammo.btDiscreteDynamicsWorld | Ammo.btSoftRigidDynamicsWorld; @@ -14,6 +15,7 @@ class _Physics { private _gravity: Vector3 = new Vector3(0, -9.8, 0); private _worldInfo: Ammo.btSoftBodyWorldInfo | null = null; private _debugDrawer: PhysicsDebugDrawer; + private _physicsDragger: PhysicsDragger; private _physicBound: BoundingBox; private _destroyObjectBeyondBounds: boolean; @@ -33,17 +35,28 @@ class _Physics { return this._debugDrawer; } + /** + * 物理拖拽器 + */ + public get physicsDragger() { + if (!this._physicsDragger) { + console.warn('To enable the dragger, set useDrag: true in Physics.init() during initialization.'); + } + return this._physicsDragger; + } + public TEMP_TRANSFORM: Ammo.btTransform; // Temp cache, save results from body.getWorldTransform() /** * 初始化物理引擎和相关配置。 * * @param options - 初始化选项参数对象。 - * @param options.useSoftBody - 是否启用软体模拟,目前仅支持布料软体类型。 + * @param options.useSoftBody - 是否启用软体模拟。 + * @param options.useDrag - 是否启用刚体拖拽功能。 * @param options.physicBound - 物理边界,默认范围:2000 2000 2000,超出边界时将会销毁该刚体。 * @param options.destroyObjectBeyondBounds - 是否在超出边界时销毁3D对象。默认 `false` 仅销毁刚体。 */ - public async init(options: { useSoftBody?: boolean, physicBound?: Vector3, destroyObjectBeyondBounds?: boolean } = {}) { + public async init(options: { useSoftBody?: boolean, useDrag?: boolean, physicBound?: Vector3, destroyObjectBeyondBounds?: boolean } = {}) { await Ammo.bind(window)(Ammo); TempPhyMath.init(); @@ -51,6 +64,8 @@ class _Physics { this.TEMP_TRANSFORM = new Ammo.btTransform(); this.initWorld(options.useSoftBody); + if (options.useDrag) this._physicsDragger = new PhysicsDragger(); + this._isInited = true; this._destroyObjectBeyondBounds = options.destroyObjectBeyondBounds; this._physicBound = new BoundingBox(new Vector3(), options.physicBound || new Vector3(2000, 2000, 2000)); diff --git a/packages/physics/index.ts b/packages/physics/index.ts index 6c0793b2..d88506ff 100644 --- a/packages/physics/index.ts +++ b/packages/physics/index.ts @@ -9,6 +9,7 @@ export * from './rigidbody/RigidbodyEnum'; export * from './rigidbody/Rigidbody'; export * from './rigidbody/GhostTrigger'; export * from './softbody/ClothSoftbody'; +export * from './softbody/RopeSoftbody'; export * from './constraint/ConeTwistConstraint'; export * from './constraint/FixedConstraint'; export * from './constraint/Generic6DofConstraint'; diff --git a/packages/physics/package.json b/packages/physics/package.json index a5a9a1f2..90d0db53 100644 --- a/packages/physics/package.json +++ b/packages/physics/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/physics", - "version": "0.3.2", + "version": "0.3.3", "author": "Orillusion", "description": "Orillusion Physics Plugin, Powerd by Ammo.js", "main": "./dist/physics.umd.js", @@ -20,8 +20,10 @@ "type": "git", "url": "git+https://github.com/Orillusion/orillusion.git" }, + "dependencies": { + "@orillusion/ammo": ">=0.2.1" + }, "peerDependencies": { - "@orillusion/ammo": ">=0.2.1", "@orillusion/core": ">=0.8.0" } } diff --git a/packages/physics/rigidbody/Rigidbody.ts b/packages/physics/rigidbody/Rigidbody.ts index 7934806b..fc8cbe4c 100644 --- a/packages/physics/rigidbody/Rigidbody.ts +++ b/packages/physics/rigidbody/Rigidbody.ts @@ -452,7 +452,7 @@ export class Rigidbody extends ComponentBase { */ public set velocity(value: Vector3) { this._velocity.copyFrom(value); - this._btRigidbody?.applyForce(TempPhyMath.toBtVec(value), TempPhyMath.zeroBtVec(TempPhyMath.tmpVecB)); + this.wait().then(rb => rb.applyForce(TempPhyMath.toBtVec(this._velocity), TempPhyMath.zeroBtVec(TempPhyMath.tmpVecB))); } /** @@ -468,7 +468,8 @@ export class Rigidbody extends ComponentBase { * Set the angular velocity value of current object */ public set angularVelocity(value: Vector3) { - this._btRigidbody?.setAngularVelocity(TempPhyMath.toBtVec(value)) + this._angularVelocity.copyFrom(value) + this.wait().then(rb => rb.setAngularVelocity(TempPhyMath.toBtVec(this._angularVelocity))); } /** * Get the linear velocity value of current object @@ -483,7 +484,8 @@ export class Rigidbody extends ComponentBase { * Set the linear velocity value of current object */ public set linearVelocity(value: Vector3) { - this._btRigidbody?.setLinearVelocity(TempPhyMath.toBtVec(value)) + this._linearVelocity.copyFrom(value) + this.wait().then(rb => rb.setLinearVelocity(TempPhyMath.toBtVec(this._linearVelocity))); } /** * Get mass value diff --git a/packages/physics/softbody/ClothSoftbody.ts b/packages/physics/softbody/ClothSoftbody.ts index dcf1c13b..749ebcfa 100644 --- a/packages/physics/softbody/ClothSoftbody.ts +++ b/packages/physics/softbody/ClothSoftbody.ts @@ -1,6 +1,5 @@ -// ClothSoftbody.ts - -import { Vector3, MeshRenderer, PlaneGeometry, ComponentBase, VertexAttributeName, Quaternion } from '@orillusion/core'; +import { Vector3, PlaneGeometry, VertexAttributeName, Quaternion } from '@orillusion/core'; +import { SoftbodyBase } from './SoftbodyBase'; import { Ammo, Physics } from '../Physics'; import { TempPhyMath } from '../utils/TempPhyMath'; import { Rigidbody } from '../rigidbody/Rigidbody'; @@ -10,149 +9,60 @@ import { Rigidbody } from '../rigidbody/Rigidbody'; */ export type CornerType = 'leftTop' | 'rightTop' | 'leftBottom' | 'rightBottom' | 'left' | 'right' | 'top' | 'bottom' | 'center'; -export class ClothSoftbody extends ComponentBase { - private _initResolve!: () => void; - private _initializationPromise: Promise = new Promise(r => this._initResolve = r); - private _btBodyInited: boolean = false; - private _btSoftbody: Ammo.btSoftBody; // 创建的 Ammo 软体实例 - private _btRigidbody: Ammo.btRigidBody; // 通过锚点附加的 Ammo 刚体实例 - private _anchorRigidbody: Rigidbody; +export class ClothSoftbody extends SoftbodyBase { + protected declare _geometry: PlaneGeometry; private _segmentW: number; private _segmentH: number; - private _geometry: PlaneGeometry; - private _diff: Vector3 = new Vector3(); + private _offset: Vector3 = new Vector3(); + private _btRigidbody: Ammo.btRigidBody; // 通过锚点附加的 Ammo 刚体实例 /** - * 布料四个角的位置 (00,01,10,11) + * 布料的四个角,默认以平面法向量计算各角。 */ public clothCorners: [Vector3, Vector3, Vector3, Vector3]; /** - * 软体的总质量 - * @default 1 - */ - public mass: number = 1; - - /** - * 软体的碰撞边距 - * @default 0.05 - */ - public margin: number = 0.05; - - /** - * 固定布料的节点 + * 固定节点索引。 */ public fixNodeIndices: CornerType[] | number[] = []; /** - * 布料的锚点 + * 添加锚点时需要的刚体。 */ - public anchorIndices: CornerType[] | number[] = []; + public anchorRigidbody: Rigidbody; /** - * 锚定的影响力。影响力值越大,软体节点越紧密地跟随刚体的运动。通常,这个值在0到1之间 - * @default 0.5 + * 布料的锚点。 */ - public influence: number | number[] = 0.5; - - /** - * 是否禁用锚定节点与刚体之间的碰撞,将其设置为true可以防止锚定节点和刚体之间发生物理碰撞 - * @default false - */ - public disableCollision: boolean | boolean[] = false; - - /** - * 当没有附加(锚定)到刚体时,应用绝对位置,否则是基于刚体的相对位置 - */ - public applyPosition: Vector3 = new Vector3(); - - /** - * 当没有附加(锚定)到刚体时,应用绝对旋转,否则是基于刚体的相对旋转 - */ - public applyRotation: Vector3 = new Vector3(); + public anchorIndices: CornerType[] | number[] = []; /** - * 碰撞组 - * @default 1 + * 仅在设置 `anchorRigidbody` 后有效,表示布料软体相对刚体的位置。 */ - public group: number = 1; + public anchorPosition: Vector3 = new Vector3(); /** - * 碰撞掩码 - * @default -1 + * 仅在设置 `anchorRigidbody` 后有效,表示布料软体相对刚体的旋转。 */ - public mask: number = -1; + public anchorRotation: Vector3 = new Vector3(); - /** - * 添加锚点时需要的刚体 - */ - public get anchorRigidbody(): Rigidbody { - return this._anchorRigidbody; - } - - public set anchorRigidbody(value: Rigidbody) { - this._anchorRigidbody = value; - this._diff.set(0, 0, 0); - } - - public get btBodyInited(): boolean { - return this._btBodyInited; - } - - /** - * return the soft body instance - */ - public get btSoftbody(): Ammo.btSoftBody { - return this._btSoftbody; - } - - /** - * Asynchronously retrieves the fully initialized soft body instance. - */ - public async wait(): Promise { - await this._initializationPromise; - return this._btSoftbody; - } + async start(): Promise { - /** - * 停止软体运动 - */ - public stopSoftBodyMovement(): void { - const nodes = this._btSoftbody.get_m_nodes(); - for (let i = 0; i < nodes.size(); i++) { - const node = nodes.at(i); - node.get_m_v().setValue(0, 0, 0); - node.get_m_f().setValue(0, 0, 0); + if (!(this._geometry instanceof PlaneGeometry)) { + throw new Error('The cloth softbody requires plane geometry.'); } - } - init(): void { - - if (!Physics.isSoftBodyWord) { - throw new Error('Enable soft body simulation by setting Physics.init({useSoftBody: true}) during initialization.'); + if (this.anchorRigidbody) { + this._btRigidbody = await this.anchorRigidbody.wait(); } + this._segmentW = this._geometry.segmentW; + this._segmentH = this._geometry.segmentH; - let geometry = this.object3D.getComponent(MeshRenderer).geometry; - if (!(geometry instanceof PlaneGeometry)) throw new Error('The cloth softbody requires plane geometry'); - this._geometry = geometry; - this._segmentW = geometry.segmentW; - this._segmentH = geometry.segmentH; + super.start() } - async start(): Promise { + protected initSoftBody(): Ammo.btSoftBody { - if (this._anchorRigidbody) { - this._btRigidbody = await this._anchorRigidbody.wait(); - } - - this.initSoftBody(); - - this._btBodyInited = true; - this._initResolve(); - } - - private initSoftBody(): void { - // Defines the four corners of the cloth let clothCorner00: Ammo.btVector3, clothCorner01: Ammo.btVector3, @@ -160,12 +70,25 @@ export class ClothSoftbody extends ComponentBase { clothCorner11: Ammo.btVector3; if (!this.clothCorners) { + const up = this._geometry.up; + let right = up.equals(Vector3.X_AXIS) ? Vector3.BACK : Vector3.X_AXIS; + + right = up.crossProduct(right).normalize(); + const forward = right.crossProduct(up).normalize(); + const halfWidth = this._geometry.width / 2; const halfHeight = this._geometry.height / 2; - clothCorner00 = TempPhyMath.setBtVec(-halfWidth, halfHeight, 0, TempPhyMath.tmpVecA); - clothCorner01 = TempPhyMath.setBtVec(halfWidth, halfHeight, 0, TempPhyMath.tmpVecB); - clothCorner10 = TempPhyMath.setBtVec(-halfWidth, -halfHeight, 0, TempPhyMath.tmpVecC); - clothCorner11 = TempPhyMath.setBtVec(halfWidth, -halfHeight, 0, TempPhyMath.tmpVecD); + + const corner00 = right.mul(halfWidth).add(forward.mul(-halfHeight)); // leftTop + const corner01 = right.mul(halfWidth).add(forward.mul(halfHeight)); // rightTop + const corner10 = right.mul(-halfWidth).add(forward.mul(-halfHeight)); // leftBottom + const corner11 = right.mul(-halfWidth).add(forward.mul(halfHeight)); // rightBottom + + clothCorner00 = TempPhyMath.toBtVec(corner00, TempPhyMath.tmpVecA); + clothCorner01 = TempPhyMath.toBtVec(corner01, TempPhyMath.tmpVecB); + clothCorner10 = TempPhyMath.toBtVec(corner10, TempPhyMath.tmpVecC); + clothCorner11 = TempPhyMath.toBtVec(corner11, TempPhyMath.tmpVecD); + } else { clothCorner00 = TempPhyMath.toBtVec(this.clothCorners[0], TempPhyMath.tmpVecA) clothCorner01 = TempPhyMath.toBtVec(this.clothCorners[1], TempPhyMath.tmpVecB); @@ -173,7 +96,7 @@ export class ClothSoftbody extends ComponentBase { clothCorner11 = TempPhyMath.toBtVec(this.clothCorners[3], TempPhyMath.tmpVecD); } - this._btSoftbody = new Ammo.btSoftBodyHelpers().CreatePatch( + const clothSoftbody = new Ammo.btSoftBodyHelpers().CreatePatch( Physics.worldInfo, clothCorner00, clothCorner01, @@ -185,213 +108,141 @@ export class ClothSoftbody extends ComponentBase { true ); - this.configureSoftBody(this._btSoftbody); + return clothSoftbody; + } - this._btSoftbody.setTotalMass(this.mass, false); - Ammo.castObject(this._btSoftbody, Ammo.btCollisionObject).getCollisionShape().setMargin(this.margin); - this._btSoftbody.generateBendingConstraints(2, this._btSoftbody.get_m_materials().at(0)); + protected configureSoftBody(clothSoftbody: Ammo.btSoftBody): void { + + // 软体配置 + const sbConfig = clothSoftbody.get_m_cfg(); + sbConfig.set_viterations(10); // 位置迭代次数 + sbConfig.set_piterations(10); // 位置求解器迭代次数 + + clothSoftbody.generateBendingConstraints(2, clothSoftbody.get_m_materials().at(0)); // 固定节点 - if (this.fixNodeIndices.length > 0) { - this.applyFixedNodes(this.fixNodeIndices); - } + if (this.fixNodeIndices.length > 0) this.applyFixedNodes(this.fixNodeIndices); // 添加锚点 if (this.anchorIndices.length > 0) { if (!this._btRigidbody) throw new Error('Needs a rigid body'); - this.setAnchor(); + this.applyAnchor(clothSoftbody); } else { - // 先旋转再平移,矩阵变换不满足交换律 - this._btSoftbody.rotate(TempPhyMath.eulerToBtQua(this.applyRotation)); - this._btSoftbody.translate(TempPhyMath.toBtVec(this.applyPosition)); - } - - // 布料变换将由顶点更新表示,避免影响需要重置三维对象变换 - this.transform.localPosition = Vector3.ZERO; - this.transform.localRotation = Vector3.ZERO; - - (Physics.world as Ammo.btSoftRigidDynamicsWorld).addSoftBody(this._btSoftbody, this.group, this.mask); - } - - private configureSoftBody(softBody: Ammo.btSoftBody): void { - // 设置配置参数 - let sbConfig = softBody.get_m_cfg(); - sbConfig.set_viterations(10); // 位置迭代次数 - sbConfig.set_piterations(10); // 位置求解器迭代次数 - // sbConfig.set_diterations(10); // 动力学迭代次数 - // sbConfig.set_citerations(10); // 碰撞迭代次数 - // sbConfig.set_kVCF(1.0); // 速度收敛系数 - // sbConfig.set_kDP(0.1); // 阻尼系数 - // sbConfig.set_kDG(0.0); // 阻力系数 - // sbConfig.set_kLF(0.05); // 升力系数 - // sbConfig.set_kPR(0.0); // 压力系数 - // sbConfig.set_kVC(0.0); // 体积保护系数 - // sbConfig.set_kDF(0.0); // 动力学系数 - // sbConfig.set_kMT(0.0); // 电磁系数 - // sbConfig.set_kCHR(1.0); // 刚性系数 - // sbConfig.set_kKHR(0.5); // 刚性恢复系数 - // sbConfig.set_kSHR(1.0); // 剪切刚性系数 - // sbConfig.set_kAHR(0.1); // 角度恢复系数 - // sbConfig.set_kSRHR_CL(1.0); // 拉伸刚性恢复系数 - // sbConfig.set_kSKHR_CL(0.5); // 刚性恢复系数 - // sbConfig.set_kSSHR_CL(0.1); // 剪切刚性恢复系数 - // sbConfig.set_kSR_SPLT_CL(0.5); // 拉伸分割系数 - // sbConfig.set_kSK_SPLT_CL(0.5); // 剪切分割系数 - // sbConfig.set_kSS_SPLT_CL(0.5); // 剪切分割系数 - // sbConfig.set_maxvolume(1.0); // 最大体积 - // sbConfig.set_timescale(1.0); // 时间缩放系数 - // sbConfig.set_collisions(0); // 碰撞设置 - - // 获取材质并设置参数 - const material = softBody.get_m_materials().at(0); - material.set_m_kLST(0.4); // 设置线性弹性系数 - material.set_m_kAST(0.4); // 设置角度弹性系数 - // material.set_m_kVST(0.2); // 设置体积弹性系数 - // material.set_m_flags(0); // 设置材质标志 - } - - onUpdate(): void { - if (!this._btBodyInited) return; - - // 根据锚点刚体的插值坐标平滑软体运动 - if (this._btRigidbody) { - this._btRigidbody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); - const nowPos = this._btRigidbody.getWorldTransform().getOrigin(); - - TempPhyMath.fromBtVec(Physics.TEMP_TRANSFORM.getOrigin(), Vector3.HELP_0); - TempPhyMath.fromBtVec(nowPos, Vector3.HELP_1); - Vector3.sub(Vector3.HELP_0, Vector3.HELP_1, this._diff); + clothSoftbody.rotate(TempPhyMath.eulerToBtQua(this.transform.localRotation)); + clothSoftbody.translate(TempPhyMath.toBtVec(this.transform.localPosition)); } - const vertices = this._geometry.getAttribute(VertexAttributeName.position); - const normals = this._geometry.getAttribute(VertexAttributeName.normal); - - const nodes = this._btSoftbody.get_m_nodes(); - for (let i = 0; i < nodes.size(); i++) { - const node = nodes.at(i); - const pos = node.get_m_x(); - vertices.data[3 * i] = pos.x() + this._diff.x; - vertices.data[3 * i + 1] = pos.y() + this._diff.y; - vertices.data[3 * i + 2] = pos.z() + this._diff.z; - - const normal = node.get_m_n(); - normals.data[3 * i] = normal.x(); - normals.data[3 * i + 1] = normal.y(); - normals.data[3 * i + 2] = normal.z(); - } - - this._geometry.vertexBuffer.upload(VertexAttributeName.position, vertices); - this._geometry.vertexBuffer.upload(VertexAttributeName.normal, normals); } - private setAnchor() { - const anchorIndices = typeof this.anchorIndices[0] === 'number' - ? this.anchorIndices as number[] - : this.getCornerIndices(this.anchorIndices as CornerType[]); - - const nodesSize = this._btSoftbody.get_m_nodes().size(); - anchorIndices.forEach(nodeIndex => { - if (nodeIndex < 0 || nodeIndex >= nodesSize) { - console.error(`Invalid node index ${nodeIndex} for soft body`); - return; - } - }); + private applyAnchor(clothSoftbody: Ammo.btSoftBody): void { let tm = this._btRigidbody.getWorldTransform(); TempPhyMath.fromBtVec(tm.getOrigin(), Vector3.HELP_0); - Vector3.HELP_0.add(this.applyPosition, Vector3.HELP_1); + Vector3.HELP_0.add(this.anchorPosition, Vector3.HELP_1); - TempPhyMath.fromBtQua(tm.getRotation(), Quaternion.HELP_0) - Quaternion.HELP_1.fromEulerAngles(this.applyRotation.x, this.applyRotation.y, this.applyRotation.z); + TempPhyMath.fromBtQua(tm.getRotation(), Quaternion.HELP_0); + Quaternion.HELP_1.fromEulerAngles(this.anchorRotation.x, this.anchorRotation.y, this.anchorRotation.z); Quaternion.HELP_1.multiply(Quaternion.HELP_0, Quaternion.HELP_1); - this._btSoftbody.rotate(TempPhyMath.toBtQua(Quaternion.HELP_1)); - this._btSoftbody.translate(TempPhyMath.toBtVec(Vector3.HELP_1)); + clothSoftbody.rotate(TempPhyMath.toBtQua(Quaternion.HELP_1)); + clothSoftbody.translate(TempPhyMath.toBtVec(Vector3.HELP_1)); - anchorIndices.forEach((nodeIndex, idx) => { - const influence = Array.isArray(this.influence) ? (this.influence[idx] ?? 0.5) : this.influence; - const disableCollision = Array.isArray(this.disableCollision) ? (this.disableCollision[idx] ?? false) : this.disableCollision; - this._btSoftbody.appendAnchor(nodeIndex, this._btRigidbody, disableCollision, influence); + const anchorIndices = this.getCornerIndices(this.anchorIndices); + anchorIndices.forEach((nodeIndex) => { + clothSoftbody.appendAnchor(nodeIndex, this._btRigidbody, this.disableCollision, this.influence); }); } - private getVertexIndex(x: number, y: number): number { - return y * (this._segmentW + 1) + x; - } - /** * 将 CornerType 数组转换成节点索引数组。 * @param cornerList 需要转换的 CornerType 数组。 * @returns 节点索引数组 */ - private getCornerIndices(cornerList: CornerType[]): number[] { + private getCornerIndices(cornerList: CornerType[] | number[]): number[] { + + if (typeof cornerList[0] === 'number') return cornerList as number[]; + const W = this._segmentW; const H = this._segmentH; - return cornerList.map(corner => { + return (cornerList as CornerType[]).map(corner => { switch (corner) { - case 'left': - return this.getVertexIndex(0, Math.floor(H / 2)); - case 'right': - return this.getVertexIndex(W, Math.floor(H / 2)); - case 'top': - return this.getVertexIndex(Math.floor(W / 2), 0); - case 'bottom': - return this.getVertexIndex(Math.floor(W / 2), H); - case 'center': - return this.getVertexIndex(Math.floor(W / 2), Math.floor(H / 2)); - case 'leftTop': - return 0; - case 'rightTop': - return W; - case 'leftBottom': - return this.getVertexIndex(0, H); - case 'rightBottom': - return this.getVertexIndex(W, H); - default: - throw new Error('Invalid corner'); + case 'left': return this.getVertexIndex(0, Math.floor(H / 2)); + case 'right': return this.getVertexIndex(W, Math.floor(H / 2)); + case 'top': return this.getVertexIndex(Math.floor(W / 2), 0); + case 'bottom': return this.getVertexIndex(Math.floor(W / 2), H); + case 'center': return this.getVertexIndex(Math.floor(W / 2), Math.floor(H / 2)); + case 'leftTop': return 0; + case 'rightTop': return W; + case 'leftBottom': return this.getVertexIndex(0, H); + case 'rightBottom': return this.getVertexIndex(W, H); + default: throw new Error('Invalid corner'); } }); + + } + + private getVertexIndex(x: number, y: number): number { + return y * (this._segmentW + 1) + x; } /** * 固定软体节点。 * @param fixedNodeIndices 表示需要固定的节点索引或 CornerType 数组。 */ - public applyFixedNodes(fixedNodeIndices: CornerType[] | number[]) { - // 确定索引数组 - const indexArray: number[] = typeof fixedNodeIndices[0] === 'number' - ? fixedNodeIndices as number[] - : this.getCornerIndices(fixedNodeIndices as CornerType[]); - - const nodes = this._btSoftbody.get_m_nodes(); - indexArray.forEach(i => { - if (i >= 0 && i < nodes.size()) { - nodes.at(i).get_m_v().setValue(0, 0, 0); - nodes.at(i).get_m_f().setValue(0, 0, 0); - nodes.at(i).set_m_im(0); - } else { - console.warn(`Index ${i} is out of bounds for nodes array.`); - } - }); + public applyFixedNodes(fixedNodeIndices: CornerType[] | number[]): void { + this.wait().then(() => { + const indexArray = this.getCornerIndices(fixedNodeIndices); + super.applyFixedNodes(indexArray); + }) } /** - * 清除所有锚点,软体将会从附加的刚体上脱落 + * 清除锚点,软体将会从附加的刚体上脱落 */ - public clearAnchors() { + public clearAnchors(): void { this._btSoftbody.get_m_anchors().clear(); + this._offset.set(0, 0, 0); this._btRigidbody = null; + this.anchorRigidbody = null; } - public destroy(force?: boolean): void { - if (this._btBodyInited) { - (Physics.world as Ammo.btSoftRigidDynamicsWorld).removeSoftBody(this._btSoftbody); - Ammo.destroy(this._btSoftbody); - this._btSoftbody = null; + onUpdate(): void { + if (!this._btBodyInited) return; + + // 根据锚点刚体的插值坐标平滑软体运动 + if (this._btRigidbody) { + this._btRigidbody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + const nowPos = this._btRigidbody.getWorldTransform().getOrigin(); + + TempPhyMath.fromBtVec(Physics.TEMP_TRANSFORM.getOrigin(), Vector3.HELP_0); + TempPhyMath.fromBtVec(nowPos, Vector3.HELP_1); + Vector3.sub(Vector3.HELP_0, Vector3.HELP_1, this._offset); } - this._btBodyInited = false; + + const vertices = this._geometry.getAttribute(VertexAttributeName.position); + const normals = this._geometry.getAttribute(VertexAttributeName.normal); + + const nodes = this._btSoftbody.get_m_nodes(); + for (let i = 0; i < nodes.size(); i++) { + const node = nodes.at(i); + const pos = node.get_m_x(); + vertices.data[3 * i] = pos.x() + this._offset.x; + vertices.data[3 * i + 1] = pos.y() + this._offset.y; + vertices.data[3 * i + 2] = pos.z() + this._offset.z; + + const normal = node.get_m_n(); + normals.data[3 * i] = -normal.x(); + normals.data[3 * i + 1] = -normal.y(); + normals.data[3 * i + 2] = -normal.z(); + } + + this._geometry.vertexBuffer.upload(VertexAttributeName.position, vertices); + this._geometry.vertexBuffer.upload(VertexAttributeName.normal, normals); + } + + public destroy(force?: boolean): void { this._btRigidbody = null; - this._anchorRigidbody = null; + this.anchorRigidbody = null; super.destroy(force); } } diff --git a/packages/physics/softbody/RopeSoftbody.ts b/packages/physics/softbody/RopeSoftbody.ts new file mode 100644 index 00000000..1661ec61 --- /dev/null +++ b/packages/physics/softbody/RopeSoftbody.ts @@ -0,0 +1,200 @@ +import { Vector3, VertexAttributeName, GeometryBase } from '@orillusion/core'; +import { SoftbodyBase } from './SoftbodyBase'; +import { Ammo, Physics } from '../Physics'; +import { TempPhyMath } from '../utils/TempPhyMath'; +import { Rigidbody } from '../rigidbody/Rigidbody'; + +export class RopeSoftbody extends SoftbodyBase { + /** + * 绳索两端的固定选项,默认值为 `0` + * + * `0`:两端不固定,`1`:起点固定,`2`:终点固定,`3`:两端固定 + */ + public fixeds: number = 0; + + /** + * 固定节点索引,与 `fixeds` 属性作用相同,但可以更自由的控制任意节点。 + */ + public fixNodeIndices: number[] = []; + + /** + * 绳索弹性,值越大弹性越低,通常设置为 0 到 1 之间,默认值为 `0.5`。 + */ + public elasticity: number = 0.5; + + /** + * 绳索起点处锚定的刚体,设置此项后绳索的起点将与该刚体的位置相同。 + */ + public anchorRigidbodyHead: Rigidbody; + + /** + * 绳索终点处锚定的刚体,设置此项后绳索的终点将与该刚体的位置相同。 + */ + public anchorRigidbodyTail: Rigidbody; + + /** + * 锚点的起点偏移量,表示起点与锚定的刚体之间的相对位置。 + */ + public anchorOffsetHead: Vector3 = new Vector3(); + + /** + * 锚点的终点偏移量,表示终点与锚定的刚体之间的相对位置。 + */ + public anchorOffsetTail: Vector3 = new Vector3(); + + private _positionHead: Vector3; + private _positionTail: Vector3; + + async start(): Promise { + if (this.anchorRigidbodyHead) { + const bodyA = await this.anchorRigidbodyHead.wait(); + this._positionHead = TempPhyMath.fromBtVec(bodyA.getWorldTransform().getOrigin()); + this._positionHead.add(this.anchorOffsetHead, this._positionHead); + } + if (this.anchorRigidbodyTail) { + const bodyB = await this.anchorRigidbodyTail.wait(); + this._positionTail = TempPhyMath.fromBtVec(bodyB.getWorldTransform().getOrigin()); + this._positionTail.add(this.anchorOffsetTail, this._positionTail); + } + super.start(); + } + + protected initSoftBody(): Ammo.btSoftBody { + const vertexArray = this._geometry.getAttribute(VertexAttributeName.position).data; + + this._positionHead ||= new Vector3(vertexArray[0], vertexArray[1], vertexArray[2]); + this._positionTail ||= new Vector3(vertexArray.at(-3), vertexArray.at(-2), vertexArray.at(-1)); + + const ropeStart = TempPhyMath.toBtVec(this._positionHead, TempPhyMath.tmpVecA); + const ropeEnd = TempPhyMath.toBtVec(this._positionTail, TempPhyMath.tmpVecB); + const segmentCount = this._geometry.vertexCount - 1; + + const ropeSoftbody = new Ammo.btSoftBodyHelpers().CreateRope( + Physics.worldInfo, + ropeStart, + ropeEnd, + segmentCount - 1, + this.fixeds + ); + + return ropeSoftbody; + } + + protected configureSoftBody(ropeSoftbody: Ammo.btSoftBody): void { + + // 设置软体配置与材质 + const sbConfig = ropeSoftbody.get_m_cfg(); + sbConfig.set_viterations(10); // 位置迭代次数 + sbConfig.set_piterations(10); // 位置求解器迭代次数 + + this.setElasticity(this.elasticity); + + // 固定节点 + if (this.fixNodeIndices.length > 0) this.applyFixedNodes(this.fixNodeIndices); + + // 锚定刚体 + if (this.anchorRigidbodyHead) { + const body = this.anchorRigidbodyHead.btRigidbody; + ropeSoftbody.appendAnchor(0, body, this.disableCollision, this.influence); + } + if (this.anchorRigidbodyTail) { + const body = this.anchorRigidbodyTail.btRigidbody; + ropeSoftbody.appendAnchor(this._geometry.vertexCount - 1, body, this.disableCollision, this.influence); + } + + } + + /** + * set rope elasticity to 0~1 + */ + public setElasticity(value: number): void { + this.elasticity = value; + this.wait().then(ropeSoftbody => { + const material = ropeSoftbody.get_m_materials().at(0); + material.set_m_kLST(value); // 线性弹性 + material.set_m_kAST(value); // 角度弹性 + }) + } + + /** + * 清除锚点,软体将会从附加的刚体上脱落 + * @param isPopBack 是否只删除一个锚点,当存在首尾两个锚点时,删除终点的锚点。 + */ + public clearAnchors(isPopBack?: boolean): void { + if (isPopBack) { + this._btSoftbody.get_m_anchors().pop_back(); + } else { + this._btSoftbody.get_m_anchors().clear(); + } + } + + onUpdate(): void { + + if (!this._btBodyInited) return; + + const nodes = this._btSoftbody.get_m_nodes(); + const vertices = this._geometry.getAttribute(VertexAttributeName.position); + + for (let i = 0; i < nodes.size(); i++) { + const pos = nodes.at(i).get_m_x(); + vertices.data[3 * i] = pos.x(); + vertices.data[3 * i + 1] = pos.y(); + vertices.data[3 * i + 2] = pos.z(); + } + + this._geometry.vertexBuffer.upload(VertexAttributeName.position, vertices); + + } + + + public destroy(force?: boolean): void { + this.anchorRigidbodyHead = null; + this.anchorRigidbodyTail = null; + super.destroy(force); + } + + /** + * 构建绳索(线条)几何体,注意添加材质时需要将拓扑结构 `topology` 设置为 `'line-list'`。 + * @param segmentCount 分段数 + * @param startPos 起点 + * @param endPos 终点 + * @returns GeometryBase + */ + public static buildRopeGeometry(segmentCount: number, startPos: Vector3, endPos: Vector3): GeometryBase { + + let vertices = new Float32Array((segmentCount + 1) * 3); + let indices = new Uint16Array(segmentCount * 2); + + for (let i = 0; i < segmentCount; i++) { + indices[i * 2] = i; + indices[i * 2 + 1] = i + 1; + } + + // 计算每个顶点之间的增量 + const deltaX = (endPos.x - startPos.x) / segmentCount; + const deltaY = (endPos.y - startPos.y) / segmentCount; + const deltaZ = (endPos.z - startPos.z) / segmentCount; + + for (let i = 0; i <= segmentCount; i++) { + vertices[i * 3] = startPos.x + deltaX * i; + vertices[i * 3 + 1] = startPos.y + deltaY * i; + vertices[i * 3 + 2] = startPos.z + deltaZ * i; + } + + const ropeGeometry = new GeometryBase(); + ropeGeometry.setIndices(indices); + ropeGeometry.setAttribute(VertexAttributeName.position, vertices); + ropeGeometry.addSubGeometry({ + indexStart: 0, + indexCount: indices.length, + vertexStart: 0, + vertexCount: 0, + firstStart: 0, + index: 0, + topology: 0 + }); + + return ropeGeometry; + } + +} diff --git a/packages/physics/softbody/SoftbodyBase.ts b/packages/physics/softbody/SoftbodyBase.ts new file mode 100644 index 00000000..f4a263bd --- /dev/null +++ b/packages/physics/softbody/SoftbodyBase.ts @@ -0,0 +1,170 @@ +import { ComponentBase, GeometryBase, MeshRenderer } from '@orillusion/core'; +import { Ammo, Physics } from '../Physics'; +import { ActivationState } from '../rigidbody/RigidbodyEnum'; +import { Rigidbody } from '../rigidbody/Rigidbody'; + +export abstract class SoftbodyBase extends ComponentBase { + private _initResolve!: () => void; + private _initializationPromise: Promise = new Promise(r => this._initResolve = r); + + protected _btBodyInited: boolean = false; + protected _btSoftbody: Ammo.btSoftBody; + protected _geometry: GeometryBase; + + /** + * 软体的总质量,默认值为 `1` + */ + public mass: number = 1; + + /** + * 碰撞边距,默认值为 `0.15` + */ + public margin: number = 0.15; + + /** + * 碰撞组,默认值为 `1` + */ + public group: number = 1; + + /** + * 碰撞掩码,默认值为 `-1` + */ + public mask: number = -1; + + /** + * 锚点的影响力。影响力值越大,软体节点越紧密地跟随刚体的运动。通常,这个值在0到1之间。默认值为 `1`。 + */ + public influence: number = 1; + + /** + * 是否禁用与锚定刚体之间的碰撞,默认值为 `false`。 + */ + public disableCollision: boolean = false; + + /** + * 设置软体激活状态。 + */ + public set activationState(value: ActivationState) { + this.wait().then(btSoftbody => btSoftbody.setActivationState(value)); + } + + public get btBodyInited(): boolean { + return this._btBodyInited; + } + + public get btSoftBody(): Ammo.btSoftBody { + return this._btSoftbody; + } + + init(): void { + if (!Physics.isSoftBodyWord) { + throw new Error('Enable soft body simulation by setting Physics.init({useSoftBody: true}) during initialization.'); + } + + this._geometry = this.object3D.getComponent(MeshRenderer)?.geometry; + + if (!this._geometry) { + throw new Error('SoftBody requires valid geometry.'); + } + + } + + async start(): Promise { + const btSoftbody = this._btSoftbody = this.initSoftBody(); + this.configureSoftBody(btSoftbody); + + btSoftbody.setTotalMass(this.mass, false); + Ammo.castObject(btSoftbody, Ammo.btCollisionObject).getCollisionShape().setMargin(this.margin); + (Physics.world as Ammo.btSoftRigidDynamicsWorld).addSoftBody(btSoftbody, this.group, this.mask); + + // 软体变换将由顶点更新表示,避免影响需要重置对象变换 + // this.transform.localPosition = this.transform.localRotation = Vector3.ZERO; + // this.transform.localScale = Vector3.ONE; + this.transform.worldMatrix.identity(); + + this._btBodyInited = true; + this._initResolve(); + } + + protected abstract initSoftBody(): Ammo.btSoftBody; + protected abstract configureSoftBody(softbody: Ammo.btSoftBody): void; + + /** + * Asynchronously retrieves the fully initialized soft body instance. + */ + public async wait(): Promise { + await this._initializationPromise; + return this._btSoftbody; + } + + /** + * Wraps the native soft body's `appendAnchor` method to anchor a node to a rigid body. + * @param nodeIndex - Index of the node to anchor. + * @param targetRigidbody - The rigid body to anchor to. + * @param disCollision - Optional. Disable collisions if true. + * @param influence - Optional. Anchor's influence. + */ + public appendAnchor(nodeIndex: number, targetRigidbody: Rigidbody, disCollision?: boolean, influence?: number): void { + disCollision ??= this.disableCollision; + influence ??= this.influence; + targetRigidbody.wait().then(btRigidbody => { + this.wait().then(ropeSoftbody => { + ropeSoftbody.appendAnchor(nodeIndex, btRigidbody, disCollision, influence); + }) + }) + } + + /** + * 固定软体节点。 + * @param fixedNodeIndices 需要固定的节点索引。 + */ + public applyFixedNodes(fixedNodeIndices: number[]): void { + this.wait().then(btSoftbody => { + const nodes = btSoftbody.get_m_nodes(); + fixedNodeIndices.forEach(i => { + if (i >= 0 && i < nodes.size()) { + nodes.at(i).get_m_v().setValue(0, 0, 0); + nodes.at(i).get_m_f().setValue(0, 0, 0); + nodes.at(i).set_m_im(0); + } else { + console.warn(`Index ${i} is out of bounds for nodes array.`); + } + }); + }) + } + + /** + * 清除固定节点 + * @param index 需要清除的节点索引,如果未提供,则清除所有节点。 + */ + public clearFixedNodes(index?: number): void { + const nodes = this._btSoftbody.get_m_nodes(); + const size = nodes.size(); + let inverseMass = 1 / this.mass * size; + + if (index != undefined) { + nodes.at(index).set_m_im(inverseMass); + return; + } + + for (let i = 0; i < size; i++) { + nodes.at(i).set_m_im(inverseMass); + } + } + + + public destroy(force?: boolean): void { + if (this._btBodyInited) { + if (Physics.world instanceof Ammo.btSoftRigidDynamicsWorld) { + Physics.world.removeSoftBody(this._btSoftbody); + Ammo.destroy(this._btSoftbody); + } + + this._geometry = null; + this._btSoftbody = null; + this._btBodyInited = false; + } + super.destroy(force); + } + +} diff --git a/packages/physics/utils/CollisionShapeUtil.ts b/packages/physics/utils/CollisionShapeUtil.ts index b1faf766..4b80ee3f 100644 --- a/packages/physics/utils/CollisionShapeUtil.ts +++ b/packages/physics/utils/CollisionShapeUtil.ts @@ -1,4 +1,4 @@ -import { Object3D, BoundUtil, Vector3, MeshRenderer, VertexAttributeName, PlaneGeometry, Quaternion, Matrix4, BoundingBox } from '@orillusion/core'; +import { Object3D, BoundUtil, Vector3, MeshRenderer, VertexAttributeName, PlaneGeometry, Quaternion, Matrix4, BoundingBox, BoxGeometry, SphereGeometry, CylinderGeometry } from '@orillusion/core'; import { Physics, Ammo } from '../Physics'; import { TempPhyMath } from './TempPhyMath'; @@ -113,31 +113,6 @@ export class CollisionShapeUtil { return shape; } - // 通过测试发现当前版本Ammo.btMultiSphereShape参数出现问题, - // 当前仅支持传入单个坐标,且球体们的位置会出现混乱, - // 传入坐标数组则完全无效。 - // 如果需要类似功能,可以使用复合或是网格形状。 - /** - * 创建多球体碰撞形状,适用于通过多个球体组合来近似复杂形状的情况。 - * 可以通过球体组合来创建近似椭球形状。 - * @param positions - 球体的位置数组。 - * @param radii - 球体的半径数组。 - * @returns Ammo.btMultiSphereShape - 多球体碰撞形状实例。 - */ - // public static createMultiSphereShape(positions: Vector3[], radii: number[]): Ammo.btMultiSphereShape { - // if (positions.length !== radii.length) { - // throw new Error("Positions and radii arrays must have the same length."); - // } - - // const btPositions = positions.map(pos => new Ammo.btVector3(pos.x, pos.y, pos.z)); - - // const shape = new Ammo.btMultiSphereShape((btPositions as any), radii, btPositions.length); - - // btPositions.forEach(pos => Ammo.destroy(pos)); - - // return shape; - // } - /** * 创建复合形状,将多个子形状组合成一个形状。 * @param childShapes - 包含子形状实例与位置、旋转属性的数组。 @@ -158,6 +133,107 @@ export class CollisionShapeUtil { return compoundShape; } + /** + * 根据 Object3D 对象及其子对象创建复合碰撞形状。 + * @param object3D - 三维对象,包含多个子对象。 + * @param includeParent - 是否包含父对象的几何体,默认值为 `true`。 + * @returns 复合碰撞形状。 + */ + public static createCompoundShapeFromObject(object3D: Object3D, includeParent: boolean = true) { + + const childShapes: ChildShape[] = []; + + // 处理父对象几何体 + if (includeParent) { + const shape = this.createShapeFromObject(object3D); + if (shape) { + const position = new Vector3(); + const rotation = new Quaternion(); + childShapes.push({ shape, position, rotation }); + } + } + + // 计算父对象的逆矩阵 + const parentMatrixInverse = object3D.transform.worldMatrix.clone(); + parentMatrixInverse.invert(); + + // 遍历并处理子对象 + object3D.forChild((child: Object3D) => { + const shape = this.createShapeFromObject(child); + if (shape) { + // 矩阵相乘并分解 + const childMatrix = child.transform.worldMatrix; + const localMatrix = Matrix4.help_matrix_0; + localMatrix.multiplyMatrices(parentMatrixInverse, childMatrix); + + const position = new Vector3(); + const rotation = new Quaternion(); + localMatrix.decompose('quaternion', [position, rotation as any, Vector3.HELP_0]); + childShapes.push({ shape, position, rotation }); + } + }); + + // 创建复合碰撞形状 + const compoundShape = this.createCompoundShape(childShapes); + return compoundShape; + } + + /** + * 根据 Object3D 对象的几何体类型创建相应的碰撞形状。 + * + * 仅支持Box、Sphere、Plane、Cylinder类型的几何体。对于不匹配的几何体类型,返回 btConvexHullShape 凸包形状。 + * @param object3D + * @returns Ammo.btCollisionShape + */ + public static createShapeFromObject(object3D: Object3D): Ammo.btCollisionShape | null { + + const geometry = object3D.getComponent(MeshRenderer)?.geometry; + if (!geometry) return null; + + let shape: Ammo.btCollisionShape; + let scale = Vector3.HELP_0.copyFrom(object3D.localScale); + + // 根据几何类型创建相应的碰撞形状 + switch (true) { + case geometry instanceof BoxGeometry: { + const { width, height, depth } = geometry; + const size = new Vector3(width, height, depth).scale(scale); + shape = this.createBoxShape(object3D, size); + break; + } + case geometry instanceof SphereGeometry: { + const radius = geometry.radius * scale.x; + shape = this.createSphereShape(object3D, radius); + break; + } + case geometry instanceof PlaneGeometry: { + const { width, height } = geometry; + const size = new Vector3(width, 0, height).scale(scale); + shape = this.createBoxShape(object3D, size); + break; + } + case geometry instanceof CylinderGeometry: { + const radiusBottom = geometry.radiusBottom * scale.x + const height = geometry.height * scale.y + + if (geometry.radiusTop === geometry.radiusBottom) { + shape = this.createCylinderShape(object3D, radiusBottom, height); + } else if (geometry.radiusTop <= 0.1) { + shape = this.createConeShape(object3D, radiusBottom, height); + } else { + shape = this.createConvexHullShape(object3D); + } + break; + } + default: { + shape = this.createConvexHullShape(object3D); + break; + } + } + + return shape; + } + /** * 创建高度场形状,基于平面顶点数据模拟地形。 * @param object3D - 用于创建碰撞体的三维对象。 @@ -251,7 +327,7 @@ export class CollisionShapeUtil { const { vertices, indices } = (modelVertices && modelIndices) ? { vertices: modelVertices, indices: modelIndices } - : this.getAllMeshVerticesAndIndices(object3D); + : this.getAllMeshVerticesAndIndices(object3D, false); const triangleMesh = this.buildTriangleMesh(vertices, indices); const shape = new Ammo.btConvexTriangleMeshShape(triangleMesh, true); @@ -278,7 +354,7 @@ export class CollisionShapeUtil { const { vertices, indices } = (modelVertices && modelIndices) ? { vertices: modelVertices, indices: modelIndices } - : this.getAllMeshVerticesAndIndices(object3D); + : this.getAllMeshVerticesAndIndices(object3D, false); const triangleMesh = this.buildTriangleMesh(vertices, indices); const shape = new Ammo.btBvhTriangleMeshShape(triangleMesh, true, true); @@ -305,7 +381,7 @@ export class CollisionShapeUtil { const { vertices, indices } = (modelVertices && modelIndices) ? { vertices: modelVertices, indices: modelIndices } - : this.getAllMeshVerticesAndIndices(object3D); + : this.getAllMeshVerticesAndIndices(object3D, false); const triangleMesh = this.buildTriangleMesh(vertices, indices); const shape = new Ammo.btGImpactMeshShape(triangleMesh); @@ -343,26 +419,26 @@ export class CollisionShapeUtil { /** * 获取3D对象所有网格的顶点与索引。 - * @param object3D - 三维对象,通常是模型对象。 + * @param object3D - 三维对象。 + * @param isTransformChildren - 是否将子对象的顶点转换到父对象的局部坐标系。默认值为 `true`。 * @returns 顶点数据和索引数据。 */ - public static getAllMeshVerticesAndIndices(object3D: Object3D) { - let mr = object3D.getComponents(MeshRenderer); + public static getAllMeshVerticesAndIndices(object3D: Object3D, isTransformChildren: boolean = true) { + let meshRenderers = object3D.getComponents(MeshRenderer); - if (mr.length === 1) { + if (meshRenderers.length === 1 && !isTransformChildren) { return { - vertices: mr[0].geometry.getAttribute(VertexAttributeName.position).data as Float32Array, - indices: mr[0].geometry.getAttribute(VertexAttributeName.indices).data as Uint16Array + vertices: meshRenderers[0].geometry.getAttribute(VertexAttributeName.position).data as Float32Array, + indices: meshRenderers[0].geometry.getAttribute(VertexAttributeName.indices).data as Uint16Array }; } let totalVertexLength = 0; let totalIndexLength = 0; - // 计算总顶点数和总索引数 - mr.forEach(e => { - totalVertexLength += e.geometry.getAttribute(VertexAttributeName.position).data.length; - totalIndexLength += e.geometry.getAttribute(VertexAttributeName.indices).data.length; + meshRenderers.forEach(renderer => { + totalVertexLength += renderer.geometry.getAttribute(VertexAttributeName.position).data.length; + totalIndexLength += renderer.geometry.getAttribute(VertexAttributeName.indices).data.length; }); let vertices = new Float32Array(totalVertexLength); @@ -372,13 +448,45 @@ export class CollisionShapeUtil { let indexOffset = 0; let currentIndexOffset = 0; - // 合并顶点和索引数据 - mr.forEach(e => { - let vertexArray = e.geometry.getAttribute(VertexAttributeName.position).data; + let parentMatrixInverse: Matrix4; + if (isTransformChildren) { + // 计算父对象的逆矩阵 + parentMatrixInverse = object3D.transform.worldMatrix.clone(); + parentMatrixInverse.invert(); + } + + meshRenderers.forEach(renderer => { + let vertexArray = renderer.geometry.getAttribute(VertexAttributeName.position).data; + + if (isTransformChildren) { + const childWorldMatrix = renderer.object3D.transform.worldMatrix; + + // 计算子对象相对父对象的局部变换矩阵 + let localMatrix = Matrix4.help_matrix_1; + localMatrix.multiplyMatrices(parentMatrixInverse, childWorldMatrix); + + let transformedVertexArray = new Float32Array(vertexArray.length); + + for (let index = 0; index < vertexArray.length / 3; index++) { + Vector3.HELP_0.set( + vertexArray[index * 3], + vertexArray[index * 3 + 1], + vertexArray[index * 3 + 2] + ); + + Vector3.HELP_0.applyMatrix4(localMatrix); + + transformedVertexArray[index * 3] = Vector3.HELP_0.x; + transformedVertexArray[index * 3 + 1] = Vector3.HELP_0.y; + transformedVertexArray[index * 3 + 2] = Vector3.HELP_0.z; + } + vertexArray = transformedVertexArray; + } + vertices.set(vertexArray, vertexOffset); vertexOffset += vertexArray.length; - let indexArray = e.geometry.getAttribute(VertexAttributeName.indices).data; + let indexArray = renderer.geometry.getAttribute(VertexAttributeName.indices).data; for (let i = 0; i < indexArray.length; i++) { indices[indexOffset + i] = indexArray[i] + currentIndexOffset; } @@ -395,17 +503,20 @@ export class CollisionShapeUtil { * @returns 局部包围盒 */ private static calculateLocalBoundingBox(object3D: Object3D): BoundingBox { - // 如果对象存在渲染节点(已添加至场景)并且没有子对象,直接返回其几何包围盒 if (object3D.renderNode && !object3D.numChildren) { return object3D.renderNode.geometry.bounds; } - // 通过旋转重置,计算对象及其子对象的包围盒,此时包围盒结果与局部包围盒相同 let originalRotation = object3D.localRotation.clone(); object3D.localRotation = Vector3.ZERO; let bounds = BoundUtil.genMeshBounds(object3D); object3D.localRotation = originalRotation; return bounds; + // const { x, y, z } = object3D.localRotation; + // object3D.localRotation.set(0, 0, 0); + // let bounds = BoundUtil.genMeshBounds(object3D); + // object3D.localRotation.set(x, y, z); + // return bounds; } } diff --git a/packages/physics/utils/PhysicsDragger.ts b/packages/physics/utils/PhysicsDragger.ts new file mode 100644 index 00000000..45e2cfe4 --- /dev/null +++ b/packages/physics/utils/PhysicsDragger.ts @@ -0,0 +1,197 @@ +import { Engine3D, View3D, PointerEvent3D, Vector3 } from "@orillusion/core"; +import { Ammo, Physics } from "../Physics"; +import { TempPhyMath } from "./TempPhyMath"; +import { CollisionFlags } from "../rigidbody/RigidbodyEnum"; + +/** + * PhysicsDragger 类用于通过鼠标操作拖拽3D物体。 + * 利用物理引擎中的射线检测与刚体交互,实现物体的实时拖拽效果。 + */ +export class PhysicsDragger { + private _view: View3D; + private _interactionDepth: number; + private _rigidBody: Ammo.btRigidBody; + private _rayStart: Ammo.btVector3; + private _rayEnd: Ammo.btVector3; + private _raycastResult: Ammo.ClosestRayResultCallback; + private _isDragging: boolean = false; + private _hitPoint: Vector3 = new Vector3(); + private _offset: Vector3 = new Vector3(); + private _enable: boolean = true; + + public get enable(): boolean { + return this._enable; + } + + /** + * 是否启用拖拽功能 + */ + public set enable(value: boolean) { + if (this._enable === value) return; + this._enable = value; + value ? this.registerEvents() : this.unregisterEvents(); + } + + /** + * 是否过滤静态刚体对象,默认值为 `true` + */ + public filterStatic: boolean = true; + + /** + * 设置射线过滤组 + */ + public set collisionFilterGroup(value: number) { + this._raycastResult?.set_m_collisionFilterGroup(value); + } + + /** + * 设置射线过滤掩码 + */ + public set collisionFilterMask(value: number) { + this._raycastResult?.set_m_collisionFilterMask(value); + } + + constructor() { + this.initRaycast(); + this.tryRegisterEvents(); + } + + private initRaycast() { + this._rayStart = new Ammo.btVector3(); + this._rayEnd = new Ammo.btVector3(); + this._raycastResult = new Ammo.ClosestRayResultCallback(this._rayStart, this._rayEnd); + } + + private tryRegisterEvents() { + const intervalId = setInterval(() => { + if (Engine3D.inputSystem) { + this.registerEvents(); + clearInterval(intervalId); + } + }, 100); + } + + private registerEvents() { + this._view = Engine3D.views[0]; + Engine3D.inputSystem?.addEventListener(PointerEvent3D.POINTER_DOWN, this.onMouseDown, this); + Engine3D.inputSystem?.addEventListener(PointerEvent3D.POINTER_MOVE, this.onMouseMove, this, null, 20); + Engine3D.inputSystem?.addEventListener(PointerEvent3D.POINTER_UP, this.onMouseUp, this, null, 20); + Engine3D.inputSystem?.addEventListener(PointerEvent3D.POINTER_WHEEL, this.onMouseWheel, this, null, 20); + } + + private unregisterEvents() { + Engine3D.inputSystem?.removeEventListener(PointerEvent3D.POINTER_DOWN, this.onMouseDown, this); + Engine3D.inputSystem?.removeEventListener(PointerEvent3D.POINTER_MOVE, this.onMouseMove, this); + Engine3D.inputSystem?.removeEventListener(PointerEvent3D.POINTER_UP, this.onMouseUp, this); + Engine3D.inputSystem?.removeEventListener(PointerEvent3D.POINTER_WHEEL, this.onMouseWheel, this); + + this.resetState(); + this._view = null; + } + + private onMouseDown(e: PointerEvent3D) { + if (!this._enable) return; + + if (e.mouseCode === 0) { // left key + const camera = this._view.camera; + let ray = camera.screenPointToRay(e.mouseX, e.mouseY); + + let adjustedDirection = ray.direction.normalize(); + let endPos = ray.origin.add(adjustedDirection.multiplyScalar(1000), ray.origin); + + this.resetRayCallback(this._raycastResult); + this.castRay(camera.object3D.localPosition, endPos); + + if (this._isDragging) { + e.stopImmediatePropagation(); + const worldCoordinates = camera.worldToScreenPoint(this._hitPoint, Vector3.HELP_1); + this._interactionDepth = worldCoordinates.z; + } + } + } + + private onMouseMove(e: PointerEvent3D) { + if (!this._enable || !this._isDragging) return; + + e.stopImmediatePropagation(); + this.updateRigidBody(); + } + + private onMouseUp(e: PointerEvent3D) { + if (!this._enable || !this._isDragging) return; + + if (e.mouseCode === 0) { + this.resetState(); + } + } + + private onMouseWheel(e: PointerEvent3D) { + if (!this._enable || !this._isDragging) return; + + this.updateRigidBody(); + } + + private resetRayCallback(callback: Ammo.ClosestRayResultCallback) { + callback.set_m_closestHitFraction(1); // 重置最近击中分数为最大 + callback.set_m_collisionObject(null); // 清除碰撞对象 + } + + private castRay(cameraPos: Vector3, targetPos: Vector3) { + this._rayStart.setValue(cameraPos.x, cameraPos.y, cameraPos.z); + this._rayEnd.setValue(targetPos.x, targetPos.y, targetPos.z); + + this._raycastResult.set_m_rayFromWorld(this._rayStart); + this._raycastResult.set_m_rayToWorld(this._rayEnd); + + Physics.world.rayTest(this._rayStart, this._rayEnd, this._raycastResult); + + if (this._raycastResult.hasHit()) { + const collisionObject = this._raycastResult.get_m_collisionObject(); + if (this.filterStatic && collisionObject.isStaticObject()) return; + + this._rigidBody = Ammo.castObject(collisionObject, Ammo.btRigidBody); + + // 交点 + TempPhyMath.fromBtVec(this._raycastResult.get_m_hitPointWorld(), this._hitPoint); + + this._rigidBody.setCollisionFlags(this._rigidBody.getCollisionFlags() | CollisionFlags.KINEMATIC_OBJECT); + + // 根据选中对象的位置与交点计算出偏移量 + this._rigidBody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + let originPos = TempPhyMath.fromBtVec(Physics.TEMP_TRANSFORM.getOrigin(), Vector3.HELP_0); + Vector3.sub(originPos, this._hitPoint, this._offset); + + this._isDragging = true; + document.body.style.cursor = 'grab'; + } + } + + // 更新刚体位置 + private updateRigidBody() { + let pos = this._view.camera.screenPointToWorld(Engine3D.inputSystem.mouseX, Engine3D.inputSystem.mouseY, this._interactionDepth); + + // 结合偏移量的新位置 + let newPos = pos.add(this._offset, pos); + + // 更新位置 + this._rigidBody.getMotionState().getWorldTransform(Physics.TEMP_TRANSFORM); + Physics.TEMP_TRANSFORM.setOrigin(TempPhyMath.toBtVec(newPos)); + this._rigidBody.getMotionState().setWorldTransform(Physics.TEMP_TRANSFORM); + this._rigidBody.getWorldTransform().setOrigin(Physics.TEMP_TRANSFORM.getOrigin()); // 确保静态刚体的位置信息是同步的 + + this._rigidBody.activate(true); + document.body.style.cursor = 'grabbing'; + } + + private resetState() { + if (this._rigidBody) { + this._rigidBody.setCollisionFlags(this._rigidBody.getCollisionFlags() & ~CollisionFlags.KINEMATIC_OBJECT); + this._rigidBody.activate(true); + this._rigidBody = null; + } + + this._isDragging = false; + document.body.style.cursor = 'default'; + } + +} diff --git a/samples/physics/Sample_Cloth.ts b/samples/physics/Sample_Cloth.ts new file mode 100644 index 00000000..067610f9 --- /dev/null +++ b/samples/physics/Sample_Cloth.ts @@ -0,0 +1,110 @@ +import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, LitMaterial, MeshRenderer, PlaneGeometry, Vector3, Object3DUtil } from "@orillusion/core"; +import { Graphic3D } from "@orillusion/graphic"; +import { Physics, Rigidbody, ClothSoftbody } from "@orillusion/physics"; +import dat from "dat.gui"; + +class Sample_Cloth { + async run() { + await Physics.init({ useSoftBody: true, useDrag: true }); + await Engine3D.init({ renderLoop: () => Physics.update() }); + let view = new View3D(); + view.scene = new Scene3D(); + let sky = view.scene.addComponent(AtmosphericComponent); + + view.camera = CameraUtil.createCamera3DObject(view.scene); + view.camera.perspective(60, webGPUContext.aspect, 1, 1000.0); + view.camera.object3D.addComponent(HoverCameraController).setCamera(0, -30, 20, new Vector3(0, 3, 0)); + + let lightObj3D = new Object3D(); + let sunLight = lightObj3D.addComponent(DirectLight); + sunLight.intensity = 2; + sunLight.castShadow = true; + lightObj3D.rotationX = 24; + lightObj3D.rotationY = -151; + view.scene.addChild(lightObj3D); + sky.relativeTransform = lightObj3D.transform; + + Engine3D.startRenderView(view); + + this.createScene(view.scene); + } + + createScene(scene: Scene3D) { + // create the ground and add a rigid body + let ground = Object3DUtil.GetSingleCube(30, 0, 30, 1, 1, 1); + scene.addChild(ground); + + let rigidbody = ground.addComponent(Rigidbody); + rigidbody.mass = 0; + rigidbody.shape = Rigidbody.collisionShape.createStaticPlaneShape(); + + // create shelves, cloth, and ball + this.createShelves(scene); + this.createCloth(scene); + const ballRb = this.createBall(scene); + + this.debug(scene, ballRb); + } + + + createShelves(scene: Scene3D) { + let shelf1 = Object3DUtil.GetSingleCube(0.5, 5, 0.5, 1, 1, 1); // left top + let shelf2 = shelf1.clone(); // right top + let shelf3 = shelf1.clone(); // left bottom + let shelf4 = shelf1.clone(); // right bottom + shelf1.localPosition = new Vector3(-4, 2.5, -4); + shelf2.localPosition = new Vector3(4, 2.5, -4); + shelf3.localPosition = new Vector3(-4, 2.5, 4); + shelf4.localPosition = new Vector3(4, 2.5, 4); + scene.addChild(shelf1); + scene.addChild(shelf2); + scene.addChild(shelf3); + scene.addChild(shelf4); + } + + createCloth(scene: Scene3D) { + const cloth = new Object3D(); + let meshRenderer = cloth.addComponent(MeshRenderer); + meshRenderer.geometry = new PlaneGeometry(8, 8, 20, 20, Vector3.UP); + let material = new LitMaterial(); + material.baseMap = Engine3D.res.redTexture; + material.cullMode = 'none'; + meshRenderer.material = material; + + cloth.y = 5; + scene.addChild(cloth); + + // add cloth softbody component + let softBody = cloth.addComponent(ClothSoftbody); + softBody.mass = 1; + softBody.margin = 0.2; + softBody.fixNodeIndices = ['leftTop', 'rightTop', 'leftBottom', 'rightBottom']; + } + + createBall(scene: Scene3D) { + const ball = Object3DUtil.GetSingleSphere(1, 0.5, 0.2, 0.8); + ball.y = 10; + scene.addChild(ball); + + let rigidbody = ball.addComponent(Rigidbody); + rigidbody.mass = 1.6; + rigidbody.shape = Rigidbody.collisionShape.createShapeFromObject(ball); + + return rigidbody; + } + + debug(scene: Scene3D, ballRb: Rigidbody) { + const graphic3D = new Graphic3D(); + scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D); + + let gui = new dat.GUI(); + let f = gui.addFolder('PhysicsDebug'); + f.add(Physics.debugDrawer, 'enable'); + f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + gui.add({ ResetBall: () => ballRb.updateTransform(new Vector3(0, 10, 0), null, true) }, 'ResetBall'); + } + +} + +new Sample_Cloth().run(); diff --git a/samples/physics/Sample_Dominoes.ts b/samples/physics/Sample_Dominoes.ts index 6cc8bc27..bae5c0a3 100644 --- a/samples/physics/Sample_Dominoes.ts +++ b/samples/physics/Sample_Dominoes.ts @@ -1,4 +1,4 @@ -import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Color, Quaternion, GridObject } from "@orillusion/core"; +import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Color, Quaternion, ExtrudeGeometry, BlendMode, BitmapTexture2D } from "@orillusion/core"; import { CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics"; import { Stats } from "@orillusion/stats"; import dat from "dat.gui"; @@ -8,44 +8,34 @@ import { Graphic3D } from '@orillusion/graphic' * Sample class demonstrating the creation of a domino effect with physics interactions. */ class Sample_Dominoes { - scene: Scene3D; - gui: dat.GUI; - async run() { // init physics and engine - await Physics.init(); + await Physics.init({ useDrag: true }); await Engine3D.init({ renderLoop: () => Physics.update() }); - Engine3D.setting.shadow.updateFrameRate = 1; - Engine3D.setting.shadow.shadowSize = 2048; - Engine3D.setting.shadow.shadowBound = 200; - let scene = this.scene = new Scene3D(); + let scene = new Scene3D(); scene.addComponent(Stats); // 启用物理调试功能时,需要为绘制器传入graphic3D对象 const graphic3D = new Graphic3D(); scene.addChild(graphic3D); Physics.initDebugDrawer(graphic3D, { enable: false }); - - this.gui = new dat.GUI(); - let f = this.gui.addFolder('PhysicsDebug'); - f.add(Physics.debugDrawer, 'enable'); - f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); - f.add(Physics, 'isStop'); - f.open(); let camera = CameraUtil.createCamera3DObject(scene); camera.perspective(60, Engine3D.aspect, 0.1, 800.0); - camera.object3D.addComponent(HoverCameraController).setCamera(0, -25, 100); + camera.object3D.addComponent(HoverCameraController).setCamera(0, -32, 80); // Create directional light let lightObj3D = new Object3D(); - lightObj3D.localRotation = new Vector3(120, 130, 50); - lightObj3D.addComponent(DirectLight).castShadow = true; + lightObj3D.localPosition = new Vector3(0, 30, -40); + lightObj3D.localRotation = new Vector3(20, 160, 0); + let directLight = lightObj3D.addComponent(DirectLight); + directLight.castShadow = true; + directLight.intensity = 2; scene.addChild(lightObj3D); // init sky - scene.addComponent(AtmosphericComponent).sunY = 0.6; + scene.addComponent(AtmosphericComponent).sunY = 0.8; let view = new View3D(); view.camera = camera; @@ -53,57 +43,93 @@ class Sample_Dominoes { Engine3D.startRenderView(view); - await this.initScene(); + await this.initScene(scene); + + this.debug(scene) } - // init the scene with ground, slide, ball, and dominoes. - private async initScene() { + // init the scene with ground, Pipe, ball, and dominoes. + private async initScene(scene: Scene3D) { // Create ground and add rigidbody - let ground = Object3DUtil.GetPlane(Engine3D.res.whiteTexture); - ground.scaleX = ground.scaleY = ground.scaleZ = 200; - - this.scene.addChild(ground); + let ground = Object3DUtil.GetSingleCube(100, 0.1, 100, 1, 1, 1); + scene.addChild(ground); let rigidbody = ground.addComponent(Rigidbody); - rigidbody.shape = CollisionShapeUtil.createStaticPlaneShape(); // Static plane shape at origin, extending infinitely upwards + rigidbody.shape = CollisionShapeUtil.createBoxShape(ground); rigidbody.mass = 0; rigidbody.friction = 100; // Set high friction for the ground rigidbody.isSilent = true; // Disable collision events - // Create dominoes - this.createDominoes(); + this.createDominoes(scene); - // init slide - await this.initSlide(); + // Create Pipe + this.createPipe(scene); // Create ball - this.createBall(); - + this.createBall(scene); } - // Load and initialize the slide model. - private async initSlide() { - let model = await Engine3D.res.loadGltf('https://raw.githubusercontent.com/ID-Emmett/static-assets/main/models/slide.glb'); - model.x = -40; - this.scene.addChild(model); + private async createPipe(scene: Scene3D) { + // create a object + const obj: Object3D = new Object3D(); + // add MeshRenderer to the object + let mr: MeshRenderer = obj.addComponent(MeshRenderer); + + // build shape + let shape: Vector3[] = [], + vertexCount = 8, + shapeRadius = 1; + for (let i = 0; i < vertexCount; i++) { + let angle = (Math.PI * 2 * i) / vertexCount; + let point = new Vector3(Math.sin(angle), 0, Math.cos(angle)).multiplyScalar(shapeRadius); + shape.push(point); + } + // build curve path + let curve: Vector3[] = [], + sectionCount = 44, + modelRadius = 4; + for (let i = 0; i < sectionCount; i++) { + let angle = (Math.PI * 2 * i) / 22; + modelRadius += (0.1 * i) / sectionCount; + let offsetY = 0.6 - Math.sqrt(i / sectionCount); + let point = new Vector3(Math.sin(angle), offsetY * 6, Math.cos(angle)).multiplyScalar(modelRadius); + curve.push(point); + } - let rigidbody = model.addComponent(Rigidbody); - rigidbody.shape = Rigidbody.collisionShape.createBvhTriangleMeshShape(model); + // build ExtrudeGeometry from shape & curve + mr.geometry = new ExtrudeGeometry().build(shape, true, curve, 0.2); + // set a pbr lit material + let material = new LitMaterial(); + material.cullMode = 'none'; + material.depthCompare = 'always'; + material.blendMode = BlendMode.ADD; + material.baseColor = new Color(0, 1, 0.5, 1.0); + material.transparent = true; + + let texture = new BitmapTexture2D(); + texture.addressModeU = 'repeat'; + texture.addressModeV = 'repeat'; + await texture.load('https://cdn.orillusion.com/textures/grid.webp'); + + material.baseMap = texture; + mr.material = material; + + obj.localPosition = new Vector3(-30, 20, -3); + scene.addChild(obj); + + let rigidbody = obj.addComponent(Rigidbody); + rigidbody.shape = CollisionShapeUtil.createBvhTriangleMeshShape(obj); rigidbody.mass = 0; - rigidbody.friction = 0.1; - // Disable debug visibility for the physics shape - rigidbody.isDisableDebugVisible = true; - this.gui.__folders['PhysicsDebug'].add(rigidbody, 'isDisableDebugVisible').listen(); } // Create a series of dominoes with rigid bodies and arrange them in an S-shaped curve. - private createDominoes() { + private createDominoes(scene: Scene3D) { const width = 0.5; const height = 5; const depth = 2; - const originX = -7; + const originX = -30; const originZ = 4.7; const totalDominoes = 40; @@ -125,9 +151,7 @@ class Sample_Dominoes { let deltaX = x - previousX; let deltaZ = z - previousZ; box.rotationY = i === 0 ? -48 : -Math.atan2(deltaZ, deltaX) * (180 / Math.PI); - - this.scene.addChild(box); - + scene.addChild(box); previousX = x; previousZ = z; @@ -143,27 +167,28 @@ class Sample_Dominoes { } // Create a ball with a rigid body. - private createBall() { - let ball = Object3DUtil.GetSingleSphere(0.8, Math.random(), Math.random(), Math.random()); + private createBall(scene: Scene3D) { + let ball = Object3DUtil.GetSingleSphere(0.8, 1, 0, 0); + ball.name = 'ball'; + ball.localPosition = new Vector3(-30, 40, 1); + scene.addChild(ball); - const originPos = new Vector3(-13.2 - 40, 28.6, 6.2); - ball.localPosition = originPos; let rigidbody = ball.addComponent(Rigidbody); rigidbody.shape = Rigidbody.collisionShape.createSphereShape(ball); rigidbody.mass = 50; - rigidbody.enablePhysicsTransformSync = true; - rigidbody.friction = 0.05; - - let f = this.gui.addFolder("ball"); - f.open(); - f.add(rigidbody, 'isKinematic').onChange(v => v || (rigidbody.enablePhysicsTransformSync = true)); - f.add({ SyncInfo: "Modify XYZ to sync rigidbody" }, "SyncInfo"); - f.add(ball.transform, 'x', -100, 100, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); - f.add(ball.transform, 'y', 0.8, 40, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); - f.add(ball.transform, 'z', -100, 100, 0.01).listen().onChange(() => rigidbody.clearForcesAndVelocities()); - f.add({ ResetPosition: () => rigidbody.updateTransform(originPos, Quaternion._zero, true) }, 'ResetPosition'); - - this.scene.addChild(ball); + } + + private debug(scene: Scene3D) { + let gui = new dat.GUI(); + let f = gui.addFolder('PhysicsDebug'); + f.add(Physics.debugDrawer, 'enable'); + f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + gui.add({ + ResetBall: () => { + const ballObj = scene.getChildByName('ball') as Object3D; + ballObj?.getComponent(Rigidbody).updateTransform(new Vector3(-30, 40, 1), Quaternion._zero, true); + } + }, 'ResetBall'); } } diff --git a/samples/physics/Sample_MultipleConstraints.ts b/samples/physics/Sample_MultipleConstraints.ts index 25183033..db0aefa5 100644 --- a/samples/physics/Sample_MultipleConstraints.ts +++ b/samples/physics/Sample_MultipleConstraints.ts @@ -1,6 +1,6 @@ -import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, BitmapTexture2D, UnLitMaterial, PlaneGeometry, GPUCullMode, Quaternion, Color } from "@orillusion/core"; +import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, PlaneGeometry, GPUCullMode, Color } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; -import { ActivationState, ClothSoftbody, CollisionShapeUtil, DebugDrawMode, FixedConstraint, Generic6DofSpringConstraint, HingeConstraint, Physics, PointToPointConstraint, Rigidbody, SliderConstraint } from "@orillusion/physics"; +import { ActivationState, CollisionShapeUtil, DebugDrawMode, FixedConstraint, HingeConstraint, Physics, PointToPointConstraint, Rigidbody, SliderConstraint, ClothSoftbody, RopeSoftbody } from "@orillusion/physics"; import dat from "dat.gui"; import { Graphic3D } from "@orillusion/graphic"; @@ -13,7 +13,7 @@ class Sample_MultipleConstraints { async run() { // init physics and engine - await Physics.init({ useSoftBody: true }); + await Physics.init({ useSoftBody: true, useDrag: true }); await Engine3D.init({ renderLoop: () => Physics.update() }); this.gui = new dat.GUI(); @@ -21,7 +21,7 @@ class Sample_MultipleConstraints { this.scene = new Scene3D(); this.scene.addComponent(Stats); - // 在引擎启动后初始化物理调试功能,需要为绘制器传入 graphic3D 对象 + // 在引擎启动后初始化物理调试功能,需要为调试器传入 graphic3D 对象 const graphic3D = new Graphic3D(); this.scene.addChild(graphic3D); Physics.initDebugDrawer(graphic3D, { @@ -36,7 +36,7 @@ class Sample_MultipleConstraints { // create directional light let light = new Object3D(); light.localRotation = new Vector3(36, -130, 60); - let dl = light.addComponent(DirectLight) + let dl = light.addComponent(DirectLight); dl.castShadow = true; dl.intensity = 3; this.scene.addChild(light); @@ -48,16 +48,19 @@ class Sample_MultipleConstraints { view.camera = camera; view.scene = this.scene; - this.physicsDebug() + this.physicsDebug(); Engine3D.startRenderView(view); - // Create ground, impactor, turntable, and chains + // Create ground, turntable, and chains this.createGround(); - await this.createImpactor(); - await this.createTurntable(); - await this.createChains(); + this.createTurntable(); + this.createChains(); + // Create impactor and softBody + let impactorRb = this.createImpactor(); + this.createClothSoftbody(impactorRb); + this.createRopeSoftbody(impactorRb); } private physicsDebug() { @@ -65,22 +68,23 @@ class Sample_MultipleConstraints { physicsFolder.add(Physics.debugDrawer, 'enable'); physicsFolder.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); physicsFolder.add(Physics, 'isStop'); + physicsFolder.add({ hint: "Drag dynamic rigid bodies with the mouse." }, "hint"); physicsFolder.open(); } private async createGround() { // Create ground - let ground = Object3DUtil.GetSingleCube(61, 2, 20, 1, 1, 1); + let ground = Object3DUtil.GetSingleCube(80, 2, 20, 1, 1, 1); ground.y = -1; // Set ground half-height this.scene.addChild(ground); // Add rigidbody to ground let groundRb = ground.addComponent(Rigidbody); - groundRb.shape = CollisionShapeUtil.createStaticPlaneShape(Vector3.UP, 1); + groundRb.shape = CollisionShapeUtil.createBoxShape(ground); groundRb.mass = 0; } - private async createImpactor() { + private createImpactor(): Rigidbody { // Create shelves const shelfSize = 0.5; const shelfHeight = 5; @@ -112,19 +116,17 @@ class Sample_MultipleConstraints { // Add rigidbody to slider let sliderRb = this.addBoxShapeRigidBody(slider, 500, true, [0.2, 0]); - // Create fulcrum - let fulcrum = Object3DUtil.GetCube(); - fulcrum.localScale = new Vector3(1, 1, 5); - fulcrum.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 3); - this.scene.addChild(fulcrum); + // Create Impactor + let impactor = Object3DUtil.GetCube(); + impactor.localScale = new Vector3(1, 1, 5); + impactor.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 3); + this.scene.addChild(impactor); - // Add rigidbody to fulcrum and initialize cloth softbody - let fulcrumRb = this.addBoxShapeRigidBody(fulcrum, 200, true); - this.initClothSoftBody(fulcrumRb); + let impactorRb = this.addBoxShapeRigidBody(impactor, 200, true); - // Create fixed constraint to attach slider to fulcrum + // Create fixed constraint to attach slider to impactor let fixedConstraint = slider.addComponent(FixedConstraint); - fixedConstraint.targetRigidbody = fulcrumRb; + fixedConstraint.targetRigidbody = impactorRb; fixedConstraint.pivotTarget = new Vector3(0, 0, -3); // Create slider constraint @@ -140,6 +142,8 @@ class Sample_MultipleConstraints { // Setup slider motor event controller this.sliderMotorEventController(shelfLeftRb, shelfRightRb, sliderConstraint); + + return impactorRb; } private sliderMotorEventController(leftRb: Rigidbody, rightRb: Rigidbody, slider: SliderConstraint) { @@ -175,7 +179,7 @@ class Sample_MultipleConstraints { folder.add(timer, 'pauseDuration', 0, 3000, 1000); } - private async createTurntable() { + private createTurntable() { // Create turntable components const columnWidth = 0.5; const columnHeight = 4.75 - columnWidth / 2; @@ -184,38 +188,38 @@ class Sample_MultipleConstraints { let column = Object3DUtil.GetCube(); column.localScale = new Vector3(columnWidth, columnHeight, columnDepth); column.localPosition = new Vector3(0, columnHeight / 2, 8); + this.scene.addChild(column); + this.addBoxShapeRigidBody(column, 0); // Add rigidbodies to turntable components - let arm1 = Object3DUtil.GetCube(); - arm1.localScale = new Vector3(10, 0.5, 0.5); - arm1.localPosition = new Vector3(0, columnHeight + columnWidth / 2, 8); - let arm2 = arm1.clone(); - arm2.y += 10; // Ensure no overlap before adding constraints - arm2.rotationY = 45; + // Create arm compound shape + let armParent = new Object3D(); + armParent.localPosition = new Vector3(0, columnHeight + columnWidth / 2, 8); - this.scene.addChild(column); - this.scene.addChild(arm1); - this.scene.addChild(arm2); + let armChild1 = Object3DUtil.GetCube(); + armChild1.rotationY = 45; + armChild1.localScale = new Vector3(10, 0.5, 0.5); + + let armChild2 = armChild1.clone(); + armChild2.rotationY = 135; - // Add rigidbodies to turntable components - this.addBoxShapeRigidBody(column, 0); - let arm1Rb = this.addBoxShapeRigidBody(arm1, 500, true); - let arm2Rb = this.addBoxShapeRigidBody(arm2, 500, true); + armParent.addChild(armChild1); + armParent.addChild(armChild2); + this.scene.addChild(armParent); + + let armRigidbody = armParent.addComponent(Rigidbody); + armRigidbody.shape = CollisionShapeUtil.createCompoundShapeFromObject(armParent); + armRigidbody.mass = 500; + armRigidbody.activationState = ActivationState.DISABLE_DEACTIVATION; // Create hinge constraint to attach arm1 to column let hinge = column.addComponent(HingeConstraint); - hinge.targetRigidbody = arm1Rb; + hinge.targetRigidbody = armRigidbody; hinge.pivotSelf.set(0, columnHeight / 2 + columnWidth / 2, 0); hinge.enableAngularMotor(true, 5, 50); - - // Create fixed constraint to attach arm2 to arm1 - let fixedConstraint = arm2.addComponent(FixedConstraint); - fixedConstraint.targetRigidbody = arm1Rb; - fixedConstraint.rotationTarget.fromEulerAngles(0, 90, 0); - fixedConstraint.pivotTarget.set(0, 0, 0); } - private async createChains() { + private createChains() { const chainHeight = 1; let chainLink = Object3DUtil.GetCube(); @@ -277,15 +281,14 @@ class Sample_MultipleConstraints { p2p.pivotSelf.y = sphereRadius; } - private async initClothSoftBody(anchorRb: Rigidbody) { + private createClothSoftbody(anchorRb: Rigidbody) { const cloth = new Object3D(); let meshRenderer = cloth.addComponent(MeshRenderer); - meshRenderer.geometry = new PlaneGeometry(3, 3, 10, 10); + meshRenderer.geometry = new PlaneGeometry(3, 3, 10, 10, Vector3.X_AXIS); // Set the plane direction to determine the four corners let material = new LitMaterial(); material.baseMap = Engine3D.res.redTexture; material.cullMode = GPUCullMode.none; meshRenderer.material = material; - this.scene.addChild(cloth); // Add cloth softbody component @@ -295,18 +298,46 @@ class Sample_MultipleConstraints { softBody.anchorRigidbody = anchorRb; // Anchor rigidbody softBody.anchorIndices = ['leftTop', 'top', 'rightTop']; // Anchor points softBody.influence = 1; // Attachment influence - softBody.disableCollision = false; // Enable collision with anchor - softBody.applyPosition = new Vector3(0, -2.1, 0); // Relative position to anchor - softBody.applyRotation = new Vector3(0, 90, 0); // Relative rotation to anchor + softBody.disableCollision = false; // Enable collision with rigidbody + softBody.anchorPosition = new Vector3(0, -2.1, 0); // Relative position to anchor - // Configure softbody parameters softBody.wait().then(btSoftbody => { - let sbConfig = btSoftbody.get_m_cfg(); + // native softbody API + let sbConfig = btSoftbody.get_m_cfg(); // configure softbody parameters sbConfig.set_kDF(0.2); sbConfig.set_kDP(0.01); sbConfig.set_kLF(0.02); sbConfig.set_kDG(0.001); }); + + } + + private createRopeSoftbody(headRb: Rigidbody) { + + const box = Object3DUtil.GetSingleCube(1, 1, 1, 1, 1, 1); + box.localPosition = new Vector3(0, 10, 0); + this.scene.addChild(box); + let tailRb = this.addBoxShapeRigidBody(box, 1, true, [0.2, 0.2]); + + const rope = new Object3D(); + let mr = rope.addComponent(MeshRenderer); + let startPos = new Vector3(0, 4.75, 3); + let endPos = new Vector3(0, 10, 0); + mr.geometry = RopeSoftbody.buildRopeGeometry(10, startPos, endPos); + + mr.material = new LitMaterial(); + mr.material.topology = 'line-list'; + this.scene.addChild(rope); + + // Add rope softbody component + let softBody = rope.addComponent(RopeSoftbody); + softBody.mass = 1; + softBody.elasticity = 0.1; + softBody.anchorRigidbodyHead = headRb; + softBody.anchorOffsetHead = new Vector3(0, -0.5, 2.1); + softBody.anchorRigidbodyTail = tailRb; + softBody.anchorOffsetTail = new Vector3(0, 0.5, 0); + } private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean, damping?: [number, number]) { diff --git a/samples/physics/Sample_PhysicsBox.ts b/samples/physics/Sample_PhysicsBox.ts index 8befd643..c69e0610 100644 --- a/samples/physics/Sample_PhysicsBox.ts +++ b/samples/physics/Sample_PhysicsBox.ts @@ -75,7 +75,7 @@ class Sample_PhysicsBox { let collider = sphere.addComponent(ColliderComponent); collider.shape = new SphereColliderShape(sphereGeo.radius); - sphere.addComponent(Rigidbody); + sphere.addComponent(Rigidbody).mass = 0.5; this.scene.addChild(sphere); } diff --git a/samples/physics/Sample_Rope.ts b/samples/physics/Sample_Rope.ts new file mode 100644 index 00000000..87a4dab1 --- /dev/null +++ b/samples/physics/Sample_Rope.ts @@ -0,0 +1,129 @@ +import { Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, HoverCameraController, Object3D, DirectLight, LitMaterial, MeshRenderer, Vector3, Object3DUtil, Color, } from "@orillusion/core"; +import { Graphic3D } from "@orillusion/graphic"; +import { Physics, Rigidbody, RopeSoftbody } from "@orillusion/physics"; +import dat from "dat.gui"; + +class Sample_Rope { + async run() { + await Physics.init({ useSoftBody: true, useDrag: true }); + await Engine3D.init({ renderLoop: () => Physics.update() }); + let view = new View3D(); + view.scene = new Scene3D(); + let sky = view.scene.addComponent(AtmosphericComponent); + + view.camera = CameraUtil.createCamera3DObject(view.scene); + view.camera.perspective(60, webGPUContext.aspect, 1, 1000.0); + view.camera.object3D.addComponent(HoverCameraController).setCamera(0, -30, 20, new Vector3(0, 3, 0)); + + let lightObj3D = new Object3D(); + let sunLight = lightObj3D.addComponent(DirectLight); + sunLight.intensity = 2; + sunLight.castShadow = true; + lightObj3D.rotationX = 24; + lightObj3D.rotationY = -151; + view.scene.addChild(lightObj3D); + sky.relativeTransform = lightObj3D.transform; + + Engine3D.startRenderView(view); + + this.createScene(view.scene); + } + + createScene(scene: Scene3D) { + // create the ground and add a rigid body + let ground = Object3DUtil.GetSingleCube(30, 0, 30, 1, 1, 1); + scene.addChild(ground); + + let rigidbody = ground.addComponent(Rigidbody); + rigidbody.mass = 0; + rigidbody.shape = Rigidbody.collisionShape.createStaticPlaneShape(); + + // create shelves + this.createShelves(scene); + + // create balls and ropes + for (let i = 0; i < 7; i++) { + let pos = new Vector3(6 - i * 2, 8, 0); + + // check if this is the last ball (tail) + let ballRb = this.createBall(scene, pos, i === 6); + + // create the rope connected to the ball + this.createRope(scene, pos, ballRb); + } + + this.debug(scene); + } + + + createShelves(scene: Scene3D) { + let shelf1 = Object3DUtil.GetSingleCube(0.2, 8, 0.2, 1, 1, 1); // left + let shelf2 = Object3DUtil.GetSingleCube(0.2, 8, 0.2, 1, 1, 1); // right + let shelf3 = Object3DUtil.GetSingleCube(20.2, 0.2, 0.2, 1, 1, 1); // top + shelf1.localPosition = new Vector3(-10, 4, 0); + shelf2.localPosition = new Vector3(10, 4, 0); + shelf3.localPosition = new Vector3(0, 8, 0); + scene.addChild(shelf1); + scene.addChild(shelf2); + scene.addChild(shelf3); + } + + createBall(scene: Scene3D, pos: Vector3, isTail: boolean) { + const ball = Object3DUtil.GetSingleSphere(0.82, 1, 1, 1); + ball.x = pos.x - (isTail ? 3 : 0); + ball.y = pos.y / 3 + (isTail ? 1.16 : 0); + scene.addChild(ball); + + let rigidbody = ball.addComponent(Rigidbody); + rigidbody.shape = Rigidbody.collisionShape.createShapeFromObject(ball); + rigidbody.mass = 1.1; + rigidbody.restitution = 1.13; + + // ball collision event to change color + let ballMaterial = ball.getComponent(MeshRenderer).material as LitMaterial; + + let timer: number | null = null; + rigidbody.collisionEvent = (contactPoint, selfBody, otherBody) => { + if (timer !== null) clearTimeout(timer); + else ballMaterial.baseColor = new Color(Color.SALMON); + + timer = setTimeout(() => { + ballMaterial.baseColor = Color.COLOR_WHITE; + timer = null; + }, 100); + } + + return rigidbody; + } + + createRope(scene: Scene3D, pos: Vector3, tailRb: Rigidbody) { + let ropeObj = new Object3D(); + let mr = ropeObj.addComponent(MeshRenderer); + mr.material = new LitMaterial(); + mr.material.topology = 'line-list'; + mr.geometry = RopeSoftbody.buildRopeGeometry(10, pos, new Vector3(0, 0, 0)); + scene.addChild(ropeObj); + + // add rope softbody component + let ropeSoftbody = ropeObj.addComponent(RopeSoftbody); + ropeSoftbody.fixeds = 1; // fixed top + ropeSoftbody.mass = 1.0; + ropeSoftbody.elasticity = 1; + ropeSoftbody.anchorRigidbodyTail = tailRb; + ropeSoftbody.anchorOffsetTail.set(0, 0.82, 0); // 0.82 is ball radius + } + + debug(scene: Scene3D) { + const graphic3D = new Graphic3D(); + scene.addChild(graphic3D); + Physics.initDebugDrawer(graphic3D); + + let gui = new dat.GUI(); + let f = gui.addFolder('PhysicsDebug'); + f.add(Physics.debugDrawer, 'enable'); + f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList); + } + +} + +new Sample_Rope().run(); diff --git a/samples/physics/Sample_dofSpringConstraint.ts b/samples/physics/Sample_dofSpringConstraint.ts index 2fe769f6..06d205c1 100644 --- a/samples/physics/Sample_dofSpringConstraint.ts +++ b/samples/physics/Sample_dofSpringConstraint.ts @@ -1,19 +1,16 @@ -import { Engine3D, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Quaternion, GridObject } from "@orillusion/core"; +import { Engine3D, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Quaternion } from "@orillusion/core"; import { Stats } from "@orillusion/stats"; import { ActivationState, CollisionShapeUtil, DebugDrawMode, Generic6DofSpringConstraint, Physics, Rigidbody } from "@orillusion/physics"; import dat from "dat.gui"; import { Graphic3D } from "@orillusion/graphic"; -/** - * Sample class demonstrating the use of multiple constraints in a physics simulation. - */ -class Sample_MultipleConstraints { +class Sample_dofSpringConstraint { scene: Scene3D; gui: dat.GUI; async run() { // Initialize physics and engine - await Physics.init(); + await Physics.init({ useDrag: true }); await Engine3D.init({ renderLoop: () => Physics.update() }); let scene = this.scene = new Scene3D(); @@ -222,4 +219,4 @@ class Sample_MultipleConstraints { } } -new Sample_MultipleConstraints().run(); +new Sample_dofSpringConstraint().run(); From c4b8626c91937d50fba1a2d94101e84053a83d4c Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 28 Aug 2024 17:56:44 +0800 Subject: [PATCH 24/25] fix: fix frameRate and camera resize --- samples/compute/fluid/shader/addforce.wgsl.ts | 2 +- src/Engine3D.ts | 86 +++++------- .../controller/HoverCameraController.ts | 3 +- src/core/Camera3D.ts | 128 ++++++++++-------- 4 files changed, 111 insertions(+), 108 deletions(-) diff --git a/samples/compute/fluid/shader/addforce.wgsl.ts b/samples/compute/fluid/shader/addforce.wgsl.ts index 7a986391..82994e83 100644 --- a/samples/compute/fluid/shader/addforce.wgsl.ts +++ b/samples/compute/fluid/shader/addforce.wgsl.ts @@ -38,7 +38,7 @@ export class addforce { fn kernel (position: vec3, radius: f32, direction: vec3, origin: vec3) -> f32{ var distanceToMouseRay: f32 = length(cross(direction, position - origin)); var normalizedDistance = max(0.0, distanceToMouseRay / radius); - return smoothstep(1.0, 0.9, normalizedDistance); + return smoothstep(0.9, 1.0, normalizedDistance); } // fn _mod (x: f32, y: f32) -> f32{ diff --git a/src/Engine3D.ts b/src/Engine3D.ts index 18fc299d..e69ca851 100644 --- a/src/Engine3D.ts +++ b/src/Engine3D.ts @@ -58,8 +58,6 @@ export class Engine3D { public static views: View3D[]; private static _frameRateValue: number = 0; private static _frameRate: number = 360; - private static _frameTimeCount: number = 0; - private static _deltaTime: number = 0; private static _time: number = 0; private static _beforeRender: Function; private static _renderLoop: Function; @@ -78,7 +76,7 @@ export class Engine3D { */ public static set frameRate(value: number) { this._frameRate = value; - this._frameRateValue = 1.0 / value; + this._frameRateValue = 1000 / value; if (value >= 360) { this._frameRateValue = 0; } @@ -376,30 +374,30 @@ export class Engine3D { return; } - /** - * set render view and start renderer - * @param view - * @returns - */ - public static startRenderView(view: View3D) { - this.renderJobs ||= new Map(); - this.views = [view]; + private static startRenderJob(view: View3D){ let renderJob = new ForwardRenderJob(view); this.renderJobs.set(view, renderJob); - let presentationSize = webGPUContext.presentationSize; - // RTResourceMap.createRTTexture(RTResourceConfig.colorBufferTex_NAME, presentationSize[0], presentationSize[1], GPUTextureFormat.rgba16float, false); if (this.setting.pick.mode == `pixel`) { let postProcessing = view.scene.getOrAddComponent(PostProcessingComponent); postProcessing.addPost(FXAAPost); - - } else { } if (this.setting.pick.mode == `pixel` || this.setting.pick.mode == `bound`) { view.enablePick = true; } + return renderJob; + } + /** + * set render view and start renderer + * @param view + * @returns + */ + public static startRenderView(view: View3D) { + this.renderJobs ||= new Map(); + this.views = [view]; + let renderJob = this.startRenderJob(view); this.resume(); return renderJob; } @@ -414,21 +412,7 @@ export class Engine3D { this.renderJobs ||= new Map(); this.views = views; for (let i = 0; i < views.length; i++) { - const view = views[i]; - let renderJob = new ForwardRenderJob(view); - this.renderJobs.set(view, renderJob); - let presentationSize = webGPUContext.presentationSize; - - if (this.setting.pick.mode == `pixel`) { - let postProcessing = view.scene.addComponent(PostProcessingComponent); - postProcessing.addPost(FXAAPost); - } else { - RTResourceMap.createRTTexture(RTResourceConfig.colorBufferTex_NAME, presentationSize[0], presentationSize[1], GPUTextureFormat.rgba16float, false); - } - - if (this.setting.pick.mode == `pixel` || this.setting.pick.mode == `bound`) { - view.enablePick = true; - } + this.startRenderJob(views[i]) } this.resume(); } @@ -446,7 +430,7 @@ export class Engine3D { * Pause the engine render */ public static pause() { - if (this._requestAnimationFrameID != 0) { + if (this._requestAnimationFrameID !== 0) { cancelAnimationFrame(this._requestAnimationFrameID); this._requestAnimationFrameID = 0; } @@ -463,23 +447,26 @@ export class Engine3D { * start engine render * @internal */ - private static render(time) { - this._deltaTime = time - this._time; - this._time = time; - + private static async render(time: number) { if (this._frameRateValue > 0) { - this._frameTimeCount += this._deltaTime * 0.001; - if (this._frameTimeCount >= this._frameRateValue * 0.95) { - this._frameTimeCount = 0; - this.updateFrame(time); + let delta = time - this._time; + while(delta < this._frameRateValue){ + let t = performance.now() + await Promise.resolve().then(()=>{ + time += (performance.now() - t) + delta = time - this._time + }) } + this._time = time; + await this.updateFrame(time); } else { - this.updateFrame(time); + await this.updateFrame(time); } - this.resume(); + this.resume() + } - private static updateFrame(time: number) { + private static async updateFrame(time: number) { Time.delta = time - Time.time; Time.time = time; Time.frame += 1; @@ -493,10 +480,10 @@ export class Engine3D { view.scene.waitUpdate(); let [w, h] = webGPUContext.presentationSize; view.camera.viewPort.setTo(0, 0, w, h); - view.camera.resetPerspective(webGPUContext.aspect); } - if (this._beforeRender) this._beforeRender(); + if (this._beforeRender) + await this._beforeRender(); /****** auto start with component list *****/ // ComponentCollect.startComponents(); @@ -555,14 +542,10 @@ export class Engine3D { } if (this._renderLoop) { - this._renderLoop(); + await this._renderLoop(); } - // console.log("useCount", Matrix4.useCount); - // let t = performance.now(); WasmMatrix.updateAllContinueTransform(0, Matrix4.useCount, 16); - // this.divB.innerText = "wasm:" + (performance.now() - t).toFixed(2); - /****** auto update global matrix share buffer write to gpu *****/ let globalMatrixBindGroup = GlobalBindGroup.modelMatrixBindGroup; globalMatrixBindGroup.writeBuffer(Matrix4.useCount * 16); @@ -587,8 +570,7 @@ export class Engine3D { } } - if (this._lateRender) this._lateRender(); + if (this._lateRender) + await this._lateRender(); } - - } diff --git a/src/components/controller/HoverCameraController.ts b/src/components/controller/HoverCameraController.ts index bd317ee1..f3c2cafa 100644 --- a/src/components/controller/HoverCameraController.ts +++ b/src/components/controller/HoverCameraController.ts @@ -283,8 +283,7 @@ export class HoverCameraController extends ComponentBase { this._tempPos = Vector3Ex.mulScale(this._tempDir, this._distance, this._tempPos); this._tempPos.add(this._currentPos.transform.localPosition, this._tempPos); - this.transform.lookAt(this._tempPos, this._currentPos.transform.localPosition, Vector3.UP); - this.camera.lookTarget.copy(this._currentPos.transform.localPosition); + this.camera.lookAt(this._tempPos, this._currentPos.transform.localPosition, Vector3.UP); } /** diff --git a/src/core/Camera3D.ts b/src/core/Camera3D.ts index 021979f8..f575076e 100644 --- a/src/core/Camera3D.ts +++ b/src/core/Camera3D.ts @@ -14,6 +14,7 @@ import { CubeCamera } from './CubeCamera'; import { webGPUContext } from '../gfx/graphics/webGpu/Context3D'; import { FrustumCSM } from './csm/FrustumCSM'; import { CSM } from './csm/CSM'; +import { CResizeEvent } from '../event/CResizeEvent'; /** * Camera components @@ -24,7 +25,7 @@ export class Camera3D extends ComponentBase { /** * camera Perspective */ - public fov: number = 1; + public fov: number = 60; /** * camera use name @@ -46,6 +47,31 @@ export class Camera3D extends ComponentBase { */ public far: number = 5000; + /** + * orth camera right plane + */ + public left: number = -100; + + /** + * orth camera left plane + */ + public right: number = 100; + + /** + * orth camera top plane + */ + public top: number = 100; + + /** + * orth camera bottom plane + */ + public bottom: number = -100; + + /** + * orth view size + */ + public frustumSize: number = 100; + /** * camera view port size */ @@ -126,21 +152,35 @@ export class Camera3D extends ComponentBase { this._enableCSM = value; } constructor() { - super(); + super(); } public init() { super.init(); this._ray = new Ray(); this.frustum = new Frustum(); + this.lookTarget = new Vector3(0, 0, 0); + // TODO: set viewport based on View3D size this.viewPort.x = 0; this.viewPort.y = 0; this.viewPort.w = webGPUContext.presentationSize[0]; this.viewPort.h = webGPUContext.presentationSize[1]; - this.lookTarget = new Vector3(0, 0, 0); - this.perspective(60, webGPUContext.aspect, 1, 1000.0); + this.updateProjection(); + webGPUContext.addEventListener(CResizeEvent.RESIZE, this.updateProjection, this) + } + + public updateProjection() { + this.aspect = webGPUContext.aspect; + if (this.type == CameraType.perspective) { + this.perspective(this.fov, this.aspect, this.near, this.far); + }else if(this.type == CameraType.ortho) { + if(this.frustumSize) + this.ortho(this.frustumSize, this.near, this.far); + else + this.orthoOffCenter(this.left, this.right, this.bottom, this.top, this.near, this.far); + } } public getShadowBias(depthTexSize: number): number { @@ -189,54 +229,47 @@ export class Camera3D extends ComponentBase { public perspective(fov: number, aspect: number, near: number, far: number) { this.fov = fov; this.aspect = aspect; - this.near = near; + this.near = Math.max(0.001, near); this.far = far; - this._projectionMatrix.perspective(fov, aspect, near, far); + this._projectionMatrix.perspective(this.fov, this.aspect, this.near, this.far); this.type = CameraType.perspective; } - public resetPerspective(aspect: number) { - if (this.type == CameraType.perspective) { - this._projectionMatrix.perspective(this.fov, aspect, this.near, this.far); - } - } - /** - * Create an orthographic camera - * @param width screen width - * @param height screen height - * @param znear camera near plane - * @param zfar camera far plane + * set an orthographic camera with a frustumSize + * @param frustumSize the frustum size + * @param near camera near plane + * @param far camera far plane */ - public ortho(width: number, height: number, znear: number, zfar: number) { - this.near = Math.max(znear, 0.1); - this.far = zfar; - this._projectionMatrix.ortho(width, height, znear, zfar); - this.type = CameraType.ortho; + public ortho(frustumSize: number, near: number, far: number) { + this.frustumSize = frustumSize; + let w = frustumSize * 0.5 * this.aspect; + let h = frustumSize * 0.5; + let left = -w / 2; + let right = w / 2; + let top = h / 2; + let bottom = -h / 2; + this.orthoOffCenter(left, right, bottom, top, near, far); } /** - * - * Create an orthographic camera - * @param l - * @param r - * @param b - * @param t - * @param zn camera near plane - * @param zf camera far plane - */ - public orthoOffCenter(l: number, r: number, b: number, t: number, zn: number, zf: number) { - this.near = Math.max(zn, 0.01); - this.far = zf; - this._projectionMatrix.orthoOffCenter(l, r, b, t, zn, zf); - this.type = CameraType.ortho; - } - - public orthoZo(l: number, r: number, b: number, t: number, zn: number, zf: number) { - this.near = Math.max(zn, 0.01); - this.far = zf; - this._projectionMatrix.orthoZO(l, r, b, t, zn, zf); + * set an orthographic camera with specified frustum space + * @param left camera left plane + * @param right camera right plane + * @param bottom camera bottom plane + * @param top camera top plane + * @param near camera near plane + * @param far camera far plane + */ + public orthoOffCenter(left: number, right: number, bottom: number, top: number, near: number, far: number){ + this.near = near; + this.far = far; + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; this.type = CameraType.ortho; + this._projectionMatrix.orthoOffCenter(this.left, this.right, this.bottom, this.top, this.near, this.far); } /** @@ -451,21 +484,10 @@ export class Camera3D extends ComponentBase { if (target) this.lookTarget.copyFrom(target); } - /** - * @internal - */ - public resetProjectMatrix() { - this.perspective(this.fov, this.aspect, this.near, this.far); - } - /** * @internal */ public onUpdate() { - if (this.type == CameraType.perspective) { - this.aspect = webGPUContext.aspect; - this.resetProjectMatrix(); - } if (this._useJitterProjection) { this.getJitteredProjectionMatrix(); } From 917acd391e6f92f6da6ecc99ea5200ccc5b24a17 Mon Sep 17 00:00:00 2001 From: ShuangLiu Date: Wed, 28 Aug 2024 17:59:37 +0800 Subject: [PATCH 25/25] bump v0.8.3 --- CHANGELOG.md | 23 +++++++++++++++++++++++ package.json | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91c7ff7d..2b133c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## [0.8.3](https://github.com/Orillusion/orillusion/compare/v0.8.2...v0.8.3) (2024-08-28) + + +### Bug Fixes + +* fix frameRate and camera resize ([c4b8626](https://github.com/Orillusion/orillusion/commit/c4b8626c91937d50fba1a2d94101e84053a83d4c)) +* fix InstanceDraw destroy error ([4529594](https://github.com/Orillusion/orillusion/commit/4529594491e111e2d98ca4319189215614d97654)) +* **GUI:** add option to receive post effects ([#426](https://github.com/Orillusion/orillusion/issues/426)) ([af74bb1](https://github.com/Orillusion/orillusion/commit/af74bb1c14a1ee42af749868271f9b45a65c2384)) +* **inputsystem:** capture pointer on pointerdown ([#432](https://github.com/Orillusion/orillusion/issues/432)) ([cc90b82](https://github.com/Orillusion/orillusion/commit/cc90b82d4d9ab8250553263e3c0499a84e3e503c)) +* **shadow:** fix acceptShadow ([4d6a838](https://github.com/Orillusion/orillusion/commit/4d6a8387310381d158fc13bc168cc7482cc656b3)) +* **transform:** fix lookAt at vertical angle ([#431](https://github.com/Orillusion/orillusion/issues/431)) ([1922f18](https://github.com/Orillusion/orillusion/commit/1922f185f67b450dcbb04216ee30dfba8cc0e0a2)) + + +### Features + +* add GridObject ([#436](https://github.com/Orillusion/orillusion/issues/436)) ([a939ce6](https://github.com/Orillusion/orillusion/commit/a939ce62ccbe3e6db6e964ebcf2921d975b23a1c)) +* **geometry:** add extra geometry package, extrude geometry and text geometry ([#442](https://github.com/Orillusion/orillusion/issues/442)) ([069e6d4](https://github.com/Orillusion/orillusion/commit/069e6d40d4510be09dfe3c7af9ac1b97bb855ccd)) +* **graphic:** move graphic3D to @orillusion/graphic ([#427](https://github.com/Orillusion/orillusion/issues/427)) ([a1d1b2a](https://github.com/Orillusion/orillusion/commit/a1d1b2aa9fc0b6abc55ad7894312f1100f6b466e)) +* **physics:** add RopeSoftBody, rigidbody dragger, and enhance collisionShapeUtil ([#448](https://github.com/Orillusion/orillusion/issues/448)) ([452d730](https://github.com/Orillusion/orillusion/commit/452d730ef3377867cd81fe6d78e3a1b744c4e2b5)) +* **physics:** Refactor physics plugin with extensive enhancements and new features ([#440](https://github.com/Orillusion/orillusion/issues/440)) ([7c18db5](https://github.com/Orillusion/orillusion/commit/7c18db5157a0001c9f056e6c7a158e62ff5f0e2b)) + + + ## [0.8.2](https://github.com/Orillusion/orillusion/compare/v0.8.1...v0.8.2) (2024-07-21) diff --git a/package.json b/package.json index c7e99ea7..50bdb07e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orillusion/core", - "version": "0.8.3-dev.3", + "version": "0.8.3", "author": "Orillusion", "description": "Orillusion WebGPU Engine", "type": "module",